From aa52999289a9c7885e4162e679d25dfaa1e0529c Mon Sep 17 00:00:00 2001 From: ok-nick Date: Tue, 10 Feb 2026 16:01:07 -0500 Subject: [PATCH 01/14] feat: Allow `Builder::sign_async` future to be spawnable on Tokio runtime --- sdk/src/builder.rs | 21 ++++++++++++++++++++- sdk/src/context.rs | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index ca5fb17db..0e0a10a48 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -2453,7 +2453,10 @@ impl std::fmt::Display for Builder { mod tests { #![allow(clippy::expect_used)] #![allow(clippy::unwrap_used)] - use std::{io::Cursor, vec}; + use std::{ + io::{self, Cursor}, + vec, + }; use c2pa_macros::c2pa_test_async; use serde_json::json; @@ -2467,6 +2470,7 @@ mod tests { assertions::{c2pa_action, BoxHash, DigitalSourceType}, crypto::raw_signature::SigningAlg, hash_stream_by_alg, + maybe_send_sync::MaybeSend, settings::Settings, utils::{ test::{test_context, write_jpeg_placeholder_stream}, @@ -4577,4 +4581,19 @@ mod tests { assert!(reader.active_manifest().is_some()); } + + // Ensures that the future returned by `Builder::sign_async` implements `Send`, thus making it + // possible to spawn on a Tokio runtime. + #[test] + fn test_sign_async_future_is_send() { + fn assert_send(_: T) {} + + let signer = async_test_signer(SigningAlg::Ps256); + let mut builder = Builder::new(); + let mut src = io::empty(); + let mut dst = io::empty(); + + let future = builder.sign_async(&signer, "image/jpeg", &mut src, &mut dst); + assert_send(future); + } } diff --git a/sdk/src/context.rs b/sdk/src/context.rs index e5b239926..827325223 100644 --- a/sdk/src/context.rs +++ b/sdk/src/context.rs @@ -367,7 +367,7 @@ impl Context { /// /// The default resolver is an `AsyncGenericResolver` wrapped with `RestrictedResolver` /// to apply host filtering from the settings. - pub fn resolver_async(&self) -> &dyn AsyncHttpResolver { + pub fn resolver_async(&self) -> &(dyn AsyncHttpResolver + Sync) { match &self.async_resolver { AsyncResolverState::Custom(resolver) => resolver.as_ref(), AsyncResolverState::Default(once_lock) => once_lock.get_or_init(|| { From e9bd453a5ccea5751b201da67b4750784a8853f0 Mon Sep 17 00:00:00 2001 From: ok-nick Date: Tue, 10 Feb 2026 16:30:55 -0500 Subject: [PATCH 02/14] fix: change `MaybeSync` to `Sync` for async HTTP resolver methods --- sdk/src/context.rs | 4 ++-- sdk/src/http/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/context.rs b/sdk/src/context.rs index 827325223..36b1df1d0 100644 --- a/sdk/src/context.rs +++ b/sdk/src/context.rs @@ -331,7 +331,7 @@ impl Context { /// # Arguments /// /// * `resolver` - Any type implementing `AsyncHttpResolver` - pub fn with_resolver_async( + pub fn with_resolver_async( mut self, resolver: T, ) -> Self { @@ -339,7 +339,7 @@ impl Context { self } - pub fn set_resolver_async( + pub fn set_resolver_async( &mut self, resolver: T, ) -> Result<()> { diff --git a/sdk/src/http/mod.rs b/sdk/src/http/mod.rs index bc44f5303..148bdae8a 100644 --- a/sdk/src/http/mod.rs +++ b/sdk/src/http/mod.rs @@ -67,7 +67,7 @@ pub type BoxedAsyncResolver = Box; /// Type alias for a boxed [`AsyncHttpResolver`] without Send + Sync bounds (WASM only). #[cfg(target_arch = "wasm32")] -pub type BoxedAsyncResolver = Box; +pub type BoxedAsyncResolver = Box; mod reqwest; mod ureq; From 1a2804d8e9df87dbe583348b3d5d6e975df9d535 Mon Sep 17 00:00:00 2001 From: ok-nick Date: Wed, 11 Feb 2026 15:08:17 -0500 Subject: [PATCH 03/14] fix: return `BoxedAsyncResolver` for `Context::resolver_async` --- sdk/src/builder.rs | 2 +- sdk/src/context.rs | 12 ++++++------ sdk/src/http/mod.rs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 0e0a10a48..9750adde9 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1872,7 +1872,7 @@ impl Builder { tsa_url, &manifest_label, &signature, - self.context().resolver_async(), + self.context().resolver_async().as_ref(), ) .await?; } diff --git a/sdk/src/context.rs b/sdk/src/context.rs index 36b1df1d0..35e64cd75 100644 --- a/sdk/src/context.rs +++ b/sdk/src/context.rs @@ -24,7 +24,7 @@ enum AsyncResolverState { /// User-provided custom resolver. Custom(BoxedAsyncResolver), /// Default resolver with lazy initialization. - Default(OnceLock>), + Default(OnceLock), } /// Internal state for signer selection. @@ -331,7 +331,7 @@ impl Context { /// # Arguments /// /// * `resolver` - Any type implementing `AsyncHttpResolver` - pub fn with_resolver_async( + pub fn with_resolver_async( mut self, resolver: T, ) -> Self { @@ -339,7 +339,7 @@ impl Context { self } - pub fn set_resolver_async( + pub fn set_resolver_async( &mut self, resolver: T, ) -> Result<()> { @@ -367,14 +367,14 @@ impl Context { /// /// The default resolver is an `AsyncGenericResolver` wrapped with `RestrictedResolver` /// to apply host filtering from the settings. - pub fn resolver_async(&self) -> &(dyn AsyncHttpResolver + Sync) { + pub fn resolver_async(&self) -> &BoxedAsyncResolver { match &self.async_resolver { - AsyncResolverState::Custom(resolver) => resolver.as_ref(), + AsyncResolverState::Custom(resolver) => resolver, AsyncResolverState::Default(once_lock) => once_lock.get_or_init(|| { let inner = AsyncGenericResolver::new(); let mut resolver = RestrictedResolver::new(inner); resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone()); - resolver + Box::new(resolver) }), } } diff --git a/sdk/src/http/mod.rs b/sdk/src/http/mod.rs index 148bdae8a..bc44f5303 100644 --- a/sdk/src/http/mod.rs +++ b/sdk/src/http/mod.rs @@ -67,7 +67,7 @@ pub type BoxedAsyncResolver = Box; /// Type alias for a boxed [`AsyncHttpResolver`] without Send + Sync bounds (WASM only). #[cfg(target_arch = "wasm32")] -pub type BoxedAsyncResolver = Box; +pub type BoxedAsyncResolver = Box; mod reqwest; mod ureq; From 617bf3a4cecf880f6d869984c0d4b5ff39c5a5ce Mon Sep 17 00:00:00 2001 From: ok-nick Date: Wed, 11 Feb 2026 16:45:49 -0500 Subject: [PATCH 04/14] feat: pass HTTP resolvers to timestamp networking --- sdk/src/builder.rs | 2 +- sdk/src/context.rs | 8 +- sdk/src/cose_sign.rs | 87 +++++++++++++---- sdk/src/cose_validator.rs | 16 +++- sdk/src/crypto/cose/sign.rs | 96 +++++++++++++++---- sdk/src/crypto/cose/sigtst.rs | 12 ++- sdk/src/crypto/raw_signature/signer.rs | 16 ++-- sdk/src/crypto/time_stamp/provider.rs | 15 +-- .../async_identity_assertion_signer.rs | 9 +- .../builder/identity_assertion_signer.rs | 9 +- .../x509/async_x509_credential_holder.rs | 3 + .../identity/x509/x509_credential_holder.rs | 3 + sdk/src/settings/signer.rs | 9 +- sdk/src/signer.rs | 49 +++++++--- sdk/src/store.rs | 72 ++++++++++++-- sdk/src/utils/test.rs | 8 +- sdk/src/utils/test_signer.rs | 9 +- 17 files changed, 328 insertions(+), 95 deletions(-) diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index ca5fb17db..d67f02b2e 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1872,7 +1872,7 @@ impl Builder { tsa_url, &manifest_label, &signature, - self.context().resolver_async(), + self.context().resolver_async().as_ref(), ) .await?; } diff --git a/sdk/src/context.rs b/sdk/src/context.rs index e5b239926..35e64cd75 100644 --- a/sdk/src/context.rs +++ b/sdk/src/context.rs @@ -24,7 +24,7 @@ enum AsyncResolverState { /// User-provided custom resolver. Custom(BoxedAsyncResolver), /// Default resolver with lazy initialization. - Default(OnceLock>), + Default(OnceLock), } /// Internal state for signer selection. @@ -367,14 +367,14 @@ impl Context { /// /// The default resolver is an `AsyncGenericResolver` wrapped with `RestrictedResolver` /// to apply host filtering from the settings. - pub fn resolver_async(&self) -> &dyn AsyncHttpResolver { + pub fn resolver_async(&self) -> &BoxedAsyncResolver { match &self.async_resolver { - AsyncResolverState::Custom(resolver) => resolver.as_ref(), + AsyncResolverState::Custom(resolver) => resolver, AsyncResolverState::Default(once_lock) => once_lock.get_or_init(|| { let inner = AsyncGenericResolver::new(); let mut resolver = RestrictedResolver::new(inner); resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone()); - resolver + Box::new(resolver) }), } } diff --git a/sdk/src/cose_sign.rs b/sdk/src/cose_sign.rs index 02672b93f..af025d329 100644 --- a/sdk/src/cose_sign.rs +++ b/sdk/src/cose_sign.rs @@ -29,6 +29,7 @@ use crate::{ raw_signature::{AsyncRawSigner, RawSigner, RawSignerError, SigningAlg}, time_stamp::{AsyncTimeStampProvider, TimeStampError, TimeStampProvider}, }, + http::{AsyncHttpResolver, SyncHttpResolver}, settings::Settings, status_tracker::{ErrorBehavior, StatusTracker}, AsyncSigner, Error, Result, Signer, @@ -56,12 +57,14 @@ use crate::{ signer: &dyn AsyncSigner, box_size: usize, settings: &Settings, + http_resolver: &(dyn AsyncHttpResolver + Sync), ))] pub fn sign_claim( claim_bytes: &[u8], signer: &dyn Signer, box_size: usize, settings: &Settings, + http_resolver: &dyn SyncHttpResolver, ) -> Result> { // Must be a valid claim. let label = "dummy_label"; @@ -74,9 +77,9 @@ pub fn sign_claim( }; let signed_bytes = if _sync { - cose_sign(signer, claim_bytes, box_size, tss, settings) + cose_sign(signer, claim_bytes, box_size, tss, settings, http_resolver) } else { - cose_sign_async(signer, claim_bytes, box_size, tss, settings).await + cose_sign_async(signer, claim_bytes, box_size, tss, settings, http_resolver).await }; match signed_bytes { @@ -117,6 +120,7 @@ pub fn sign_claim( box_size: usize, time_stamp_storage: TimeStampStorage, settings: &Settings, + http_resolver: &(dyn AsyncHttpResolver + Sync), ))] pub(crate) fn cose_sign( signer: &dyn Signer, @@ -124,6 +128,7 @@ pub(crate) fn cose_sign( box_size: usize, time_stamp_storage: TimeStampStorage, settings: &Settings, + http_resolver: &dyn SyncHttpResolver, ) -> Result> { // Make sure the signing cert is valid. let certs = signer.certs()?; @@ -135,20 +140,44 @@ pub(crate) fn cose_sign( if _sync { match signer.raw_signer() { - Some(raw_signer) => Ok(sign(*raw_signer, data, Some(box_size), time_stamp_storage)?), + Some(raw_signer) => Ok(sign( + *raw_signer, + data, + Some(box_size), + time_stamp_storage, + http_resolver, + )?), None => { let wrapper = SignerWrapper(signer); - Ok(sign(&wrapper, data, Some(box_size), time_stamp_storage)?) + Ok(sign( + &wrapper, + data, + Some(box_size), + time_stamp_storage, + http_resolver, + )?) } } } else { match signer.async_raw_signer() { - Some(raw_signer) => { - Ok(sign_async(*raw_signer, data, Some(box_size), time_stamp_storage).await?) - } + Some(raw_signer) => Ok(sign_async( + *raw_signer, + data, + Some(box_size), + time_stamp_storage, + http_resolver, + ) + .await?), None => { let wrapper = AsyncSignerWrapper(signer); - Ok(sign_async(&wrapper, data, Some(box_size), time_stamp_storage).await?) + Ok(sign_async( + &wrapper, + data, + Some(box_size), + time_stamp_storage, + http_resolver, + ) + .await?) } } } @@ -219,10 +248,11 @@ impl TimeStampProvider for SignerWrapper<'_> { fn send_time_stamp_request( &self, + http_resolver: &dyn SyncHttpResolver, message: &[u8], ) -> Option, TimeStampError>> { self.0 - .send_timestamp_request(message) + .send_timestamp_request(http_resolver, message) .map(|r| r.map_err(|e| e.into())) } } @@ -273,10 +303,11 @@ impl AsyncTimeStampProvider for AsyncSignerWrapper<'_> { async fn send_time_stamp_request( &self, + http_resolver: &(dyn AsyncHttpResolver + Sync), message: &[u8], ) -> Option, TimeStampError>> { self.0 - .send_timestamp_request(message) + .send_timestamp_request(http_resolver, message) .await .map(|r| r.map_err(|e| e.into())) } @@ -294,6 +325,7 @@ mod tests { use crate::{ claim::Claim, crypto::raw_signature::SigningAlg, + http::{AsyncGenericResolver, SyncGenericResolver, SyncHttpResolver}, settings::Settings, utils::test_signer::{async_test_signer, test_signer}, Result, Signer, @@ -317,7 +349,14 @@ mod tests { let signer = test_signer(SigningAlg::Ps256); let box_size = Signer::reserve_size(signer.as_ref()); - let cose_sign1 = sign_claim(&claim_bytes, signer.as_ref(), box_size, &settings).unwrap(); + let cose_sign1 = sign_claim( + &claim_bytes, + signer.as_ref(), + box_size, + &settings, + &SyncGenericResolver::new(), + ) + .unwrap(); assert_eq!(cose_sign1.len(), box_size); } @@ -342,9 +381,15 @@ mod tests { let signer = async_test_signer(SigningAlg::Ps256); let box_size = signer.reserve_size(); - let cose_sign1 = sign_claim_async(&claim_bytes, &signer, box_size, &settings) - .await - .unwrap(); + let cose_sign1 = sign_claim_async( + &claim_bytes, + &signer, + box_size, + &settings, + &AsyncGenericResolver::new(), + ) + .await + .unwrap(); assert_eq!(cose_sign1.len(), box_size); } @@ -377,7 +422,11 @@ mod tests { 1024 } - fn send_timestamp_request(&self, _message: &[u8]) -> Option>> { + fn send_timestamp_request( + &self, + _http_resolver: &dyn SyncHttpResolver, + _message: &[u8], + ) -> Option>> { Some(Ok(Vec::new())) } } @@ -395,7 +444,13 @@ mod tests { let signer = BogusSigner::new(); - let _cose_sign1 = sign_claim(&claim_bytes, &signer, box_size, &settings); + let _cose_sign1 = sign_claim( + &claim_bytes, + &signer, + box_size, + &settings, + &SyncGenericResolver::new(), + ); assert!(_cose_sign1.is_err()); } diff --git a/sdk/src/cose_validator.rs b/sdk/src/cose_validator.rs index 5a71cdab7..5125b1fdb 100644 --- a/sdk/src/cose_validator.rs +++ b/sdk/src/cose_validator.rs @@ -263,8 +263,8 @@ pub mod tests { use super::*; use crate::{ - crypto::raw_signature::SigningAlg, settings::Settings, status_tracker::StatusTracker, - utils::test_signer::test_signer, Signer, + crypto::raw_signature::SigningAlg, http::SyncGenericResolver, settings::Settings, + status_tracker::StatusTracker, utils::test_signer::test_signer, Signer, }; #[test] @@ -283,9 +283,14 @@ pub mod tests { let signer = test_signer(SigningAlg::Ps256); - let cose_bytes = - crate::cose_sign::sign_claim(&claim_bytes, signer.as_ref(), box_size, &settings) - .unwrap(); + let cose_bytes = crate::cose_sign::sign_claim( + &claim_bytes, + signer.as_ref(), + box_size, + &settings, + &SyncGenericResolver::new(), + ) + .unwrap(); let cose_sign1 = parse_cose_sign1(&cose_bytes, &claim_bytes, &mut validation_log).unwrap(); @@ -361,6 +366,7 @@ pub mod tests { &ocsp_signer, ocsp_signer.reserve_size(), &settings, + &SyncGenericResolver::new(), ) .unwrap(); diff --git a/sdk/src/crypto/cose/sign.rs b/sdk/src/crypto/cose/sign.rs index 305a4a927..87dcce7b3 100644 --- a/sdk/src/crypto/cose/sign.rs +++ b/sdk/src/crypto/cose/sign.rs @@ -23,10 +23,13 @@ use serde_bytes::ByteBuf; use x509_parser::prelude::X509Certificate; use super::cert_chain_from_sign1; -use crate::crypto::{ - cose::{add_sigtst_header, add_sigtst_header_async, CoseError, TimeStampStorage}, - ec_utils::{der_to_p1363, ec_curve_from_public_key_der, parse_ec_der_sig}, - raw_signature::{AsyncRawSigner, RawSigner, SigningAlg}, +use crate::{ + crypto::{ + cose::{add_sigtst_header, add_sigtst_header_async, CoseError, TimeStampStorage}, + ec_utils::{der_to_p1363, ec_curve_from_public_key_der, parse_ec_der_sig}, + raw_signature::{AsyncRawSigner, RawSigner, SigningAlg}, + }, + http::{AsyncHttpResolver, SyncHttpResolver}, }; /// Given an arbitrary block of data and a [`RawSigner`] or [`AsyncRawSigner`] @@ -80,23 +83,29 @@ use crate::crypto::{ signer: &dyn AsyncRawSigner, data: &[u8], box_size: Option, - tss: TimeStampStorage + tss: TimeStampStorage, + http_resolver: &(dyn AsyncHttpResolver + Sync), ))] pub fn sign( signer: &dyn RawSigner, data: &[u8], box_size: Option, tss: TimeStampStorage, + http_resolver: &dyn SyncHttpResolver, ) -> Result, CoseError> { if _sync { match tss { - TimeStampStorage::V1_sigTst => sign_v1(signer, data, box_size, tss), - TimeStampStorage::V2_sigTst2_CTT => sign_v2(signer, data, box_size, tss), + TimeStampStorage::V1_sigTst => sign_v1(signer, data, box_size, tss, http_resolver), + TimeStampStorage::V2_sigTst2_CTT => sign_v2(signer, data, box_size, tss, http_resolver), } } else { match tss { - TimeStampStorage::V1_sigTst => sign_v1_async(signer, data, box_size, tss).await, - TimeStampStorage::V2_sigTst2_CTT => sign_v2_async(signer, data, box_size, tss).await, + TimeStampStorage::V1_sigTst => { + sign_v1_async(signer, data, box_size, tss, http_resolver).await + } + TimeStampStorage::V2_sigTst2_CTT => { + sign_v2_async(signer, data, box_size, tss, http_resolver).await + } } } } @@ -105,13 +114,15 @@ pub fn sign( signer: &dyn AsyncRawSigner, data: &[u8], box_size: Option, - tss: TimeStampStorage + tss: TimeStampStorage, + http_resolver: &(dyn AsyncHttpResolver + Sync), ))] pub fn sign_v1( signer: &dyn RawSigner, data: &[u8], box_size: Option, tss: TimeStampStorage, + http_resolver: &dyn SyncHttpResolver, ) -> Result, CoseError> { let alg = signer.alg(); @@ -126,9 +137,9 @@ pub fn sign_v1( // V1: Generate time stamp then sign. let unprotected_header = if _sync { - build_unprotected_header(signer, data, &protected_header, tss)? + build_unprotected_header(signer, data, &protected_header, tss, http_resolver)? } else { - build_unprotected_header_async(signer, data, &protected_header, tss).await? + build_unprotected_header_async(signer, data, &protected_header, tss, http_resolver).await? }; let sign1_builder = CoseSign1Builder::new() @@ -192,18 +203,37 @@ pub fn sign_v1( signer: &dyn AsyncRawSigner, data: &[u8], box_size: Option, - tss: TimeStampStorage + tss: TimeStampStorage, + http_resolver: &(dyn AsyncHttpResolver + Sync), ))] pub fn sign_v2( signer: &dyn RawSigner, data: &[u8], box_size: Option, tss: TimeStampStorage, + http_resolver: &dyn SyncHttpResolver, ) -> Result, CoseError> { if _sync { - sign_v2_embedded(signer, data, box_size, CosePayload::Detached, None, tss) + sign_v2_embedded( + signer, + data, + box_size, + CosePayload::Detached, + None, + tss, + http_resolver, + ) } else { - sign_v2_embedded_async(signer, data, box_size, CosePayload::Detached, None, tss).await + sign_v2_embedded_async( + signer, + data, + box_size, + CosePayload::Detached, + None, + tss, + http_resolver, + ) + .await } } @@ -279,7 +309,8 @@ pub enum CosePayload { box_size: Option, payload: CosePayload, content_type: Option, - tss: TimeStampStorage + tss: TimeStampStorage, + http_resolver: &(dyn AsyncHttpResolver + Sync), ))] pub fn sign_v2_embedded( signer: &dyn RawSigner, @@ -288,6 +319,7 @@ pub fn sign_v2_embedded( payload: CosePayload, content_type: Option, tss: TimeStampStorage, + http_resolver: &dyn SyncHttpResolver, ) -> Result, CoseError> { let alg = signer.alg(); @@ -363,9 +395,22 @@ pub fn sign_v2_embedded( // Fill in the unprotected header with time stamp data. let unprotected_header = if _sync { - build_unprotected_header(signer, &sig_data_cbor, &protected_header, tss)? + build_unprotected_header( + signer, + &sig_data_cbor, + &protected_header, + tss, + http_resolver, + )? } else { - build_unprotected_header_async(signer, &sig_data_cbor, &protected_header, tss).await? + build_unprotected_header_async( + signer, + &sig_data_cbor, + &protected_header, + tss, + http_resolver, + ) + .await? }; sign1.unprotected = unprotected_header; @@ -424,12 +469,21 @@ fn build_protected_header( Ok(ph2) } -#[async_generic(async_signature(signer: &dyn AsyncRawSigner, data: &[u8], p_header: &ProtectedHeader, tss: TimeStampStorage,))] +#[async_generic( + async_signature( + signer: &dyn AsyncRawSigner, + data: &[u8], + p_header: &ProtectedHeader, + tss: TimeStampStorage, + http_resolver: &(dyn AsyncHttpResolver + Sync), + )) +] fn build_unprotected_header( signer: &dyn RawSigner, data: &[u8], p_header: &ProtectedHeader, tss: TimeStampStorage, + http_resolver: &dyn SyncHttpResolver, ) -> Result { // signed_data_from_time_stamp_response @@ -438,9 +492,9 @@ fn build_unprotected_header( let unprotected_h = HeaderBuilder::new(); let mut unprotected_h = if _sync { - add_sigtst_header(signer, data, p_header, unprotected_h, tss)? + add_sigtst_header(signer, data, p_header, unprotected_h, tss, http_resolver)? } else { - add_sigtst_header_async(signer, data, p_header, unprotected_h, tss).await? + add_sigtst_header_async(signer, data, p_header, unprotected_h, tss, http_resolver).await? }; // Set the OCSP responder response if available. diff --git a/sdk/src/crypto/cose/sigtst.rs b/sdk/src/crypto/cose/sigtst.rs index 8bcf3d5ea..9f00e5b8d 100644 --- a/sdk/src/crypto/cose/sigtst.rs +++ b/sdk/src/crypto/cose/sigtst.rs @@ -30,6 +30,7 @@ use crate::{ TimeStampResponse, }, }, + http::{AsyncHttpResolver, SyncHttpResolver}, log_item, status_tracker::StatusTracker, validation_status, Result, @@ -219,20 +220,25 @@ impl TstContainer { p_header: &ProtectedHeader, mut header_builder: HeaderBuilder, tss: TimeStampStorage, - ))] + http_resolver: &(dyn AsyncHttpResolver + Sync), + )) +] pub(crate) fn add_sigtst_header( ts_provider: &dyn RawSigner, data: &[u8], p_header: &ProtectedHeader, mut header_builder: HeaderBuilder, tss: TimeStampStorage, + http_resolver: &dyn SyncHttpResolver, ) -> Result { let sd = cose_countersign_data(data, p_header); let maybe_cts = if _sync { - ts_provider.send_time_stamp_request(&sd) + ts_provider.send_time_stamp_request(http_resolver, &sd) } else { - ts_provider.send_time_stamp_request(&sd).await + ts_provider + .send_time_stamp_request(http_resolver, &sd) + .await }; if let Some(cts) = maybe_cts { diff --git a/sdk/src/crypto/raw_signature/signer.rs b/sdk/src/crypto/raw_signature/signer.rs index c4c665a12..5056a7ca6 100644 --- a/sdk/src/crypto/raw_signature/signer.rs +++ b/sdk/src/crypto/raw_signature/signer.rs @@ -268,12 +268,16 @@ impl AsyncTimeStampProvider for AsyncRawSignerWrapper { self.0.time_stamp_request_body(message) } - async fn send_time_stamp_request( - &self, - message: &[u8], - ) -> Option, TimeStampError>> { - self.0.send_time_stamp_request(message) - } + // TODO: this feels hacky but the problem is that we can't pass an AsyncHttpResolver as a SyncHttpResolver + // We create an AsyncRawSignerWrapper from the actual crypto signers which is never supposed to + // implement its own timestamping code anyways(?) because of that I thikn we can remove most of this trait impl + // async fn send_time_stamp_request( + // &self, + // http_resolver: &(dyn AsyncHttpResolver + Sync), + // message: &[u8], + // ) -> Option, TimeStampError>> { + // self.0.send_time_stamp_request(message) + // } } #[cfg(test)] diff --git a/sdk/src/crypto/time_stamp/provider.rs b/sdk/src/crypto/time_stamp/provider.rs index c66196e4e..8adddebf0 100644 --- a/sdk/src/crypto/time_stamp/provider.rs +++ b/sdk/src/crypto/time_stamp/provider.rs @@ -22,7 +22,7 @@ use crate::{ raw_signature::oids::{ans1_oid_bcder_oid, SHA256_OID}, time_stamp::TimeStampError, }, - http::SyncGenericResolver, + http::{AsyncHttpResolver, SyncHttpResolver}, maybe_send_sync::MaybeSync, }; @@ -59,7 +59,11 @@ pub trait TimeStampProvider { /// [RFC 3161]: https://datatracker.ietf.org/doc/html/rfc3161 /// /// todo: THIS CODE IS NOT COMPATIBLE WITH C2PA 2.x sigTst2 - fn send_time_stamp_request(&self, message: &[u8]) -> Option, TimeStampError>> { + fn send_time_stamp_request( + &self, + http_resolver: &dyn SyncHttpResolver, + message: &[u8], + ) -> Option, TimeStampError>> { if let Some(url) = self.time_stamp_service_url() { if let Ok(body) = self.time_stamp_request_body(message) { let headers: Option> = self.time_stamp_request_headers(); @@ -68,7 +72,7 @@ pub trait TimeStampProvider { headers, &body, message, - &SyncGenericResolver::new(), + http_resolver, )); } } @@ -115,12 +119,11 @@ pub trait AsyncTimeStampProvider: MaybeSync { /// [RFC 3161]: https://datatracker.ietf.org/doc/html/rfc3161 async fn send_time_stamp_request( &self, + http_resolver: &(dyn AsyncHttpResolver + Sync), message: &[u8], ) -> Option, TimeStampError>> { if let Some(url) = self.time_stamp_service_url() { if let Ok(body) = self.time_stamp_request_body(message) { - use crate::http::AsyncGenericResolver; - let headers: Option> = self.time_stamp_request_headers(); return Some( super::http_request::default_rfc3161_request_async( @@ -128,7 +131,7 @@ pub trait AsyncTimeStampProvider: MaybeSync { headers, &body, message, - &AsyncGenericResolver::new(), + http_resolver, ) .await, ); diff --git a/sdk/src/identity/builder/async_identity_assertion_signer.rs b/sdk/src/identity/builder/async_identity_assertion_signer.rs index 7a7a086dd..6352701ba 100644 --- a/sdk/src/identity/builder/async_identity_assertion_signer.rs +++ b/sdk/src/identity/builder/async_identity_assertion_signer.rs @@ -16,6 +16,7 @@ use async_trait::async_trait; use crate::{ crypto::raw_signature::{AsyncRawSigner, SigningAlg}, dynamic_assertion::AsyncDynamicAssertion, + http::AsyncHttpResolver, identity::builder::AsyncIdentityAssertionBuilder, AsyncSigner, Result, }; @@ -134,9 +135,13 @@ impl AsyncSigner for AsyncIdentityAssertionSigner { .map_err(|e| e.into()) } - async fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + async fn send_timestamp_request( + &self, + http_resolver: &(dyn AsyncHttpResolver + Sync), + message: &[u8], + ) -> Option>> { self.signer - .send_time_stamp_request(message) + .send_time_stamp_request(http_resolver, message) .await .map(|r| r.map_err(|e| e.into())) } diff --git a/sdk/src/identity/builder/identity_assertion_signer.rs b/sdk/src/identity/builder/identity_assertion_signer.rs index 71028dcd9..8023924f4 100644 --- a/sdk/src/identity/builder/identity_assertion_signer.rs +++ b/sdk/src/identity/builder/identity_assertion_signer.rs @@ -16,6 +16,7 @@ use std::sync::RwLock; use crate::{ crypto::raw_signature::{RawSigner, SigningAlg}, dynamic_assertion::DynamicAssertion, + http::SyncHttpResolver, identity::builder::IdentityAssertionBuilder, Result, Signer, }; @@ -111,9 +112,13 @@ impl Signer for IdentityAssertionSigner { .map_err(|e| e.into()) } - fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + fn send_timestamp_request( + &self, + http_resolver: &dyn SyncHttpResolver, + message: &[u8], + ) -> Option>> { self.signer - .send_time_stamp_request(message) + .send_time_stamp_request(http_resolver, message) .map(|r| r.map_err(|e| e.into())) } diff --git a/sdk/src/identity/x509/async_x509_credential_holder.rs b/sdk/src/identity/x509/async_x509_credential_holder.rs index d9db6d678..762ae11c2 100644 --- a/sdk/src/identity/x509/async_x509_credential_holder.rs +++ b/sdk/src/identity/x509/async_x509_credential_holder.rs @@ -18,6 +18,7 @@ use crate::{ cose::{sign_async, TimeStampStorage}, raw_signature::AsyncRawSigner, }, + http::AsyncGenericResolver, identity::{ builder::{AsyncCredentialHolder, IdentityBuilderError}, SignerPayload, @@ -91,6 +92,8 @@ impl AsyncCredentialHolder for AsyncX509CredentialHolder { &sp_cbor, None, TimeStampStorage::V2_sigTst2_CTT, + // TODO: pass in as parameter + &AsyncGenericResolver::new(), ) .await .map_err(|e| IdentityBuilderError::SignerError(e.to_string()))?) diff --git a/sdk/src/identity/x509/x509_credential_holder.rs b/sdk/src/identity/x509/x509_credential_holder.rs index ccc609de0..384de8b33 100644 --- a/sdk/src/identity/x509/x509_credential_holder.rs +++ b/sdk/src/identity/x509/x509_credential_holder.rs @@ -16,6 +16,7 @@ use crate::{ cose::{sign, TimeStampStorage}, raw_signature::RawSigner, }, + http::SyncGenericResolver, identity::{ builder::{CredentialHolder, IdentityBuilderError}, SignerPayload, @@ -64,6 +65,8 @@ impl CredentialHolder for X509CredentialHolder { &sp_cbor, None, TimeStampStorage::V2_sigTst2_CTT, + // TODO: pass in as parameter + &SyncGenericResolver::new(), ) .map_err(|e| IdentityBuilderError::SignerError(e.to_string())) } diff --git a/sdk/src/settings/signer.rs b/sdk/src/settings/signer.rs index daf16a981..1480c354c 100644 --- a/sdk/src/settings/signer.rs +++ b/sdk/src/settings/signer.rs @@ -213,8 +213,13 @@ impl Signer for CawgX509IdentitySigner { self.c2pa_signer.timestamp_request_body(message) } - fn send_timestamp_request(&self, message: &[u8]) -> Option>> { - self.c2pa_signer.send_timestamp_request(message) + fn send_timestamp_request( + &self, + http_resolver: &dyn SyncHttpResolver, + message: &[u8], + ) -> Option>> { + self.c2pa_signer + .send_timestamp_request(http_resolver, message) } fn ocsp_val(&self) -> Option> { diff --git a/sdk/src/signer.rs b/sdk/src/signer.rs index 1013ccf15..4ebd99736 100644 --- a/sdk/src/signer.rs +++ b/sdk/src/signer.rs @@ -19,7 +19,7 @@ use crate::{ time_stamp::{TimeStampError, TimeStampProvider}, }, dynamic_assertion::{AsyncDynamicAssertion, DynamicAssertion}, - http::SyncGenericResolver, + http::{AsyncHttpResolver, SyncHttpResolver}, maybe_send_sync::{MaybeSend, MaybeSync}, Result, }; @@ -87,7 +87,11 @@ pub trait Signer { /// /// The default implementation will send the request to the URL /// provided by [`Self::time_authority_url()`], if any. - fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + fn send_timestamp_request( + &self, + http_resolver: &dyn SyncHttpResolver, + message: &[u8], + ) -> Option>> { if let Some(url) = self.time_authority_url() { if let Ok(body) = self.timestamp_request_body(message) { let headers: Option> = self.timestamp_request_headers(); @@ -97,7 +101,7 @@ pub trait Signer { headers, &body, message, - &SyncGenericResolver::new(), + http_resolver, ) .map_err(|e| e.into()), ); @@ -216,11 +220,13 @@ pub trait AsyncSigner: MaybeSend + MaybeSync { /// /// The default implementation will send the request to the URL /// provided by [`Self::time_authority_url()`], if any. - async fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + async fn send_timestamp_request( + &self, + http_resolver: &(dyn AsyncHttpResolver + Sync), + message: &[u8], + ) -> Option>> { if let Some(url) = self.time_authority_url() { if let Ok(body) = self.timestamp_request_body(message) { - use crate::http::AsyncGenericResolver; - let headers: Option> = self.timestamp_request_headers(); return Some( crate::crypto::time_stamp::default_rfc3161_request_async( @@ -228,7 +234,7 @@ pub trait AsyncSigner: MaybeSend + MaybeSync { headers, &body, message, - &AsyncGenericResolver::new(), + http_resolver, ) .await .map_err(|e| e.into()), @@ -319,8 +325,12 @@ impl Signer for Box { (**self).timestamp_request_body(message) } - fn send_timestamp_request(&self, message: &[u8]) -> Option>> { - (**self).send_timestamp_request(message) + fn send_timestamp_request( + &self, + http_resolver: &dyn SyncHttpResolver, + message: &[u8], + ) -> Option>> { + (**self).send_timestamp_request(http_resolver, message) } fn raw_signer(&self) -> Option> { @@ -369,10 +379,11 @@ impl TimeStampProvider for Box { fn send_time_stamp_request( &self, + http_resolver: &dyn SyncHttpResolver, message: &[u8], ) -> Option, TimeStampError>> { self.as_ref() - .send_timestamp_request(message) + .send_timestamp_request(http_resolver, message) .map(|r| Ok(r?)) } } @@ -410,8 +421,14 @@ impl AsyncSigner for Box { (**self).timestamp_request_body(message) } - async fn send_timestamp_request(&self, message: &[u8]) -> Option>> { - (**self).send_timestamp_request(message).await + async fn send_timestamp_request( + &self, + http_resolver: &(dyn AsyncHttpResolver + Sync), + message: &[u8], + ) -> Option>> { + (**self) + .send_timestamp_request(http_resolver, message) + .await } async fn ocsp_val(&self) -> Option> { @@ -469,9 +486,13 @@ impl Signer for RawSignerWrapper { .map_err(|e| e.into()) } - fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + fn send_timestamp_request( + &self, + http_resolver: &dyn SyncHttpResolver, + message: &[u8], + ) -> Option>> { self.0 - .send_time_stamp_request(message) + .send_time_stamp_request(http_resolver, message) .map(|r| r.map_err(|e| e.into())) } diff --git a/sdk/src/store.rs b/sdk/src/store.rs index 2a47afcf4..ab93dba77 100644 --- a/sdk/src/store.rs +++ b/sdk/src/store.rs @@ -59,6 +59,7 @@ use crate::{ error::{Error, Result}, hash_utils::{hash_by_alg, vec_compare, verify_by_alg}, hashed_uri::HashedUri, + http::{AsyncHttpResolver, SyncHttpResolver}, jumbf::{ self, boxes::*, @@ -548,6 +549,7 @@ impl Store { signer: &dyn AsyncSigner, box_size: usize, settings: &Settings, + http_resolver: &(dyn AsyncHttpResolver + Sync), ))] pub fn sign_claim( &self, @@ -555,6 +557,7 @@ impl Store { signer: &dyn Signer, box_size: usize, settings: &Settings, + http_resolver: &dyn SyncHttpResolver, ) -> Result> { let claim_bytes = claim.data()?; @@ -573,7 +576,14 @@ impl Store { // Let the signer do all the COSE processing and return the structured COSE data. return signer.sign(&claim_bytes); // do not verify remote signers (we never did) } else { - cose_sign(signer, &claim_bytes, box_size, tss, &adjusted_settings) + cose_sign( + signer, + &claim_bytes, + box_size, + tss, + &adjusted_settings, + http_resolver, + ) } } else { if signer.direct_cose_handling() { @@ -581,7 +591,7 @@ impl Store { return signer.sign(claim_bytes.clone()).await; // do not verify remote signers (we never did) } else { - cose_sign_async(signer, &claim_bytes, box_size, tss, settings).await + cose_sign_async(signer, &claim_bytes, box_size, tss, settings, http_resolver).await } }; match result { @@ -2547,7 +2557,13 @@ impl Store { // sign contents let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?; - let sig = self.sign_claim(pc, signer, signer.reserve_size(), context.settings())?; + let sig = self.sign_claim( + pc, + signer, + signer.reserve_size(), + context.settings(), + context.resolver(), + )?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); @@ -2579,7 +2595,13 @@ impl Store { // sign contents let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?; let sig = self - .sign_claim_async(pc, signer, signer.reserve_size(), context.settings()) + .sign_claim_async( + pc, + signer, + signer.reserve_size(), + context.settings(), + context.resolver_async().as_ref(), + ) .await?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); @@ -2611,7 +2633,13 @@ impl Store { let mut jumbf_bytes = self.to_jumbf_internal(signer.reserve_size())?; // sign contents - let sig = self.sign_claim(pc, signer, signer.reserve_size(), context.settings())?; + let sig = self.sign_claim( + pc, + signer, + signer.reserve_size(), + context.settings(), + context.resolver(), + )?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); if sig_placeholder.len() != sig.len() { @@ -2649,7 +2677,13 @@ impl Store { // sign contents let sig = self - .sign_claim_async(pc, signer, signer.reserve_size(), context.settings()) + .sign_claim_async( + pc, + signer, + signer.reserve_size(), + context.settings(), + context.resolver_async().as_ref(), + ) .await?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); @@ -2917,7 +2951,13 @@ impl Store { // sign the claim let pc = temp_store.provenance_claim().ok_or(Error::ClaimEncoding)?; - let sig = temp_store.sign_claim(pc, signer, signer.reserve_size(), context.settings())?; + let sig = temp_store.sign_claim( + pc, + signer, + signer.reserve_size(), + context.settings(), + context.resolver(), + )?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); match temp_store.finish_save(jumbf_bytes, &dest_path, sig, &sig_placeholder) { @@ -3027,10 +3067,22 @@ impl Store { let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?; let sig = if _sync { - self.sign_claim(pc, signer, signer.reserve_size(), settings) + self.sign_claim( + pc, + signer, + signer.reserve_size(), + settings, + context.resolver(), + ) } else { - self.sign_claim_async(pc, signer, signer.reserve_size(), settings) - .await + self.sign_claim_async( + pc, + signer, + signer.reserve_size(), + settings, + context.resolver_async().as_ref(), + ) + .await }?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); diff --git a/sdk/src/utils/test.rs b/sdk/src/utils/test.rs index 02f9cf4a3..3c10c1f28 100644 --- a/sdk/src/utils/test.rs +++ b/sdk/src/utils/test.rs @@ -35,6 +35,7 @@ use crate::{ context::Context, crypto::{cose::CertificateTrustPolicy, raw_signature::SigningAlg}, hash_utils::Hasher, + http::{AsyncHttpResolver, SyncHttpResolver}, jumbf_io::get_assetio_handler, resource_store::UriOrResource, store::Store, @@ -706,7 +707,11 @@ impl crate::Signer for TestGoodSigner { 1024 } - fn send_timestamp_request(&self, _message: &[u8]) -> Option>> { + fn send_timestamp_request( + &self, + _http_resolver: &dyn SyncHttpResolver, + _message: &[u8], + ) -> Option>> { Some(Ok(Vec::new())) } } @@ -734,6 +739,7 @@ impl AsyncSigner for AsyncTestGoodSigner { async fn send_timestamp_request( &self, + _http_resolver: &(dyn AsyncHttpResolver + Sync), _message: &[u8], ) -> Option>> { Some(Ok(Vec::new())) diff --git a/sdk/src/utils/test_signer.rs b/sdk/src/utils/test_signer.rs index 92a08e521..53e838080 100644 --- a/sdk/src/utils/test_signer.rs +++ b/sdk/src/utils/test_signer.rs @@ -20,6 +20,7 @@ use crate::{ async_signer_from_cert_chain_and_private_key, signer_from_cert_chain_and_private_key, AsyncRawSigner, SigningAlg, }, + http::AsyncHttpResolver, signer::{BoxedAsyncSigner, BoxedSigner, RawSignerWrapper}, AsyncSigner, Result, }; @@ -155,9 +156,13 @@ impl AsyncSigner for AsyncRawSignerWrapper { .map_err(|e| e.into()) } - async fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + async fn send_timestamp_request( + &self, + http_resolver: &(dyn AsyncHttpResolver + Sync), + message: &[u8], + ) -> Option>> { self.0 - .send_time_stamp_request(message) + .send_time_stamp_request(http_resolver, message) .await .map(|r| r.map_err(|e| e.into())) } From 7a89ba58f47f24f45f5733081709006a340e5cb8 Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 12 Feb 2026 14:13:26 -0500 Subject: [PATCH 05/14] refactor: return arc-wrapped resolver from context --- sdk/src/assertions/timestamp.rs | 4 +-- sdk/src/builder.rs | 2 +- sdk/src/context.rs | 45 +++++++++++++++++++---------- sdk/src/http/interop.rs | 50 +++++++++++++++++++++++++++++++++ sdk/src/http/mod.rs | 23 ++------------- sdk/src/lib.rs | 3 +- sdk/src/maybe_send_sync.rs | 8 +++--- 7 files changed, 90 insertions(+), 45 deletions(-) create mode 100644 sdk/src/http/interop.rs diff --git a/sdk/src/assertions/timestamp.rs b/sdk/src/assertions/timestamp.rs index 6110f2e93..e80a74c74 100644 --- a/sdk/src/assertions/timestamp.rs +++ b/sdk/src/assertions/timestamp.rs @@ -68,7 +68,7 @@ impl TimeStamp { tsa_url: &str, manifest_id: &str, signature: &[u8], - http_resolver: &(impl AsyncHttpResolver + ?Sized), + http_resolver: &impl AsyncHttpResolver, ))] pub(crate) fn refresh_timestamp( &mut self, @@ -97,7 +97,7 @@ impl TimeStamp { #[async_generic(async_signature( tsa_url: &str, message: &[u8], - http_resolver: &(impl AsyncHttpResolver + ?Sized), + http_resolver: &impl AsyncHttpResolver, ))] pub(crate) fn send_timestamp_token_request( tsa_url: &str, diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 9750adde9..893e7c5b1 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1872,7 +1872,7 @@ impl Builder { tsa_url, &manifest_label, &signature, - self.context().resolver_async().as_ref(), + &self.context().resolver_async(), ) .await?; } diff --git a/sdk/src/context.rs b/sdk/src/context.rs index 35e64cd75..8ec7831a2 100644 --- a/sdk/src/context.rs +++ b/sdk/src/context.rs @@ -1,9 +1,22 @@ -use std::sync::OnceLock; +// Copyright 2026 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +use std::sync::{Arc, OnceLock}; use crate::{ http::{ - restricted::RestrictedResolver, AsyncGenericResolver, AsyncHttpResolver, - BoxedAsyncResolver, BoxedSyncResolver, SyncGenericResolver, SyncHttpResolver, + restricted::RestrictedResolver, AsyncGenericResolver, AsyncHttpResolver, AsyncResolver, + BoxedSyncResolver, SyncGenericResolver, SyncHttpResolver, }, maybe_send_sync::{MaybeSend, MaybeSync}, settings::Settings, @@ -22,9 +35,9 @@ enum SyncResolverState { /// Internal state for async HTTP resolver selection. enum AsyncResolverState { /// User-provided custom resolver. - Custom(BoxedAsyncResolver), + Custom(Arc), /// Default resolver with lazy initialization. - Default(OnceLock), + Default(OnceLock>), } /// Internal state for signer selection. @@ -335,7 +348,7 @@ impl Context { mut self, resolver: T, ) -> Self { - self.async_resolver = AsyncResolverState::Custom(Box::new(resolver)); + self.async_resolver = AsyncResolverState::Custom(Arc::new(resolver)); self } @@ -343,7 +356,7 @@ impl Context { &mut self, resolver: T, ) -> Result<()> { - self.async_resolver = AsyncResolverState::Custom(Box::new(resolver)); + self.async_resolver = AsyncResolverState::Custom(Arc::new(resolver)); Ok(()) } @@ -367,15 +380,17 @@ impl Context { /// /// The default resolver is an `AsyncGenericResolver` wrapped with `RestrictedResolver` /// to apply host filtering from the settings. - pub fn resolver_async(&self) -> &BoxedAsyncResolver { + pub fn resolver_async(&self) -> Arc { match &self.async_resolver { - AsyncResolverState::Custom(resolver) => resolver, - AsyncResolverState::Default(once_lock) => once_lock.get_or_init(|| { - let inner = AsyncGenericResolver::new(); - let mut resolver = RestrictedResolver::new(inner); - resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone()); - Box::new(resolver) - }), + AsyncResolverState::Custom(resolver) => resolver.clone(), + AsyncResolverState::Default(once_lock) => once_lock + .get_or_init(|| { + let inner = AsyncGenericResolver::new(); + let mut resolver = RestrictedResolver::new(inner); + resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone()); + Arc::new(resolver) + }) + .clone(), } } diff --git a/sdk/src/http/interop.rs b/sdk/src/http/interop.rs new file mode 100644 index 000000000..d0295462b --- /dev/null +++ b/sdk/src/http/interop.rs @@ -0,0 +1,50 @@ +// Copyright 2026 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +//! Type aliases and traits for boxed and arced HTTP resolvers with conditional Send + Sync bounds. +//! These are particularly useful when dealing with trait objects, such as in the case of [`Context`]. +//! +//! [`Context`]: crate::Context + +use std::{io::Read, sync::Arc}; + +use async_trait::async_trait; +use http::{Request, Response}; + +use crate::http::{AsyncHttpResolver, HttpResolverError, SyncHttpResolver}; + +/// Type alias for a boxed [`SyncHttpResolver`] with conditional Send + Sync bounds. +/// On non-WASM targets, the resolver is Send + Sync for thread-safe usage. +#[cfg(not(target_arch = "wasm32"))] +pub type BoxedSyncResolver = Box; + +/// Type alias for a boxed [`SyncHttpResolver`] without Send + Sync bounds (WASM only). +#[cfg(target_arch = "wasm32")] +pub type BoxedSyncResolver = Box; + +#[cfg(not(target_arch = "wasm32"))] +pub type AsyncResolver = dyn AsyncHttpResolver + Send + Sync; + +#[cfg(target_arch = "wasm32")] +pub type AsyncResolver = dyn AsyncHttpResolver; + +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +impl AsyncHttpResolver for Arc { + async fn http_resolve_async( + &self, + request: Request>, + ) -> Result>, HttpResolverError> { + (**self).http_resolve_async(request).await + } +} diff --git a/sdk/src/http/mod.rs b/sdk/src/http/mod.rs index bc44f5303..eebeafa82 100644 --- a/sdk/src/http/mod.rs +++ b/sdk/src/http/mod.rs @@ -48,27 +48,7 @@ use http::{Request, Response}; use crate::Result; -// Type aliases for boxed HTTP resolvers with conditional Send + Sync bounds -// These are the canonical definitions used throughout the codebase - -/// Type alias for a boxed [`SyncHttpResolver`] with conditional Send + Sync bounds. -/// On non-WASM targets, the resolver is Send + Sync for thread-safe usage. -#[cfg(not(target_arch = "wasm32"))] -pub type BoxedSyncResolver = Box; - -/// Type alias for a boxed [`SyncHttpResolver`] without Send + Sync bounds (WASM only). -#[cfg(target_arch = "wasm32")] -pub type BoxedSyncResolver = Box; - -/// Type alias for a boxed [`AsyncHttpResolver`] with conditional Send + Sync bounds. -/// On non-WASM targets, the resolver is Send + Sync for thread-safe usage. -#[cfg(not(target_arch = "wasm32"))] -pub type BoxedAsyncResolver = Box; - -/// Type alias for a boxed [`AsyncHttpResolver`] without Send + Sync bounds (WASM only). -#[cfg(target_arch = "wasm32")] -pub type BoxedAsyncResolver = Box; - +mod interop; mod reqwest; mod ureq; mod wasi; @@ -77,6 +57,7 @@ pub mod restricted; // Since we expose `http::Request` and `http::Response` in the public API, we also expose the `http` crate. pub use http; +pub use interop::*; /// A resolver for sync (blocking) HTTP requests. pub trait SyncHttpResolver { diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index d394eb66c..7b992ff86 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -211,9 +211,8 @@ pub mod crypto; #[doc(hidden)] pub mod dynamic_assertion; -// TODO: pub it when we expose in high-level API /// The `http` module contains generic traits for configuring sync and async HTTP resolvers. -pub(crate) mod http; +pub mod http; /// The `identity` module provides support for the [CAWG identity assertion](https://cawg.io/identity). #[doc(hidden)] diff --git a/sdk/src/maybe_send_sync.rs b/sdk/src/maybe_send_sync.rs index c81e0c402..898ab70e4 100644 --- a/sdk/src/maybe_send_sync.rs +++ b/sdk/src/maybe_send_sync.rs @@ -50,10 +50,10 @@ pub trait MaybeSend: Send {} pub trait MaybeSend {} #[cfg(not(target_arch = "wasm32"))] -impl MaybeSend for T {} +impl MaybeSend for T {} #[cfg(target_arch = "wasm32")] -impl MaybeSend for T {} +impl MaybeSend for T {} /// A trait that is `Sync` on non-WASM targets and not `Sync` on WASM targets. /// @@ -83,7 +83,7 @@ pub trait MaybeSync: Sync {} pub trait MaybeSync {} #[cfg(not(target_arch = "wasm32"))] -impl MaybeSync for T {} +impl MaybeSync for T {} #[cfg(target_arch = "wasm32")] -impl MaybeSync for T {} +impl MaybeSync for T {} From fe274bd18b49c0bd42c475dfbfa27955511594b3 Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 12 Feb 2026 14:46:34 -0500 Subject: [PATCH 06/14] docs: add back docs for HTTP resolver types --- sdk/src/http/interop.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/src/http/interop.rs b/sdk/src/http/interop.rs index d0295462b..a7570e559 100644 --- a/sdk/src/http/interop.rs +++ b/sdk/src/http/interop.rs @@ -32,9 +32,12 @@ pub type BoxedSyncResolver = Box; #[cfg(target_arch = "wasm32")] pub type BoxedSyncResolver = Box; +/// Type alias for a trait object [`AsyncHttpResolver`] with Send + Sync bounds. +/// On non-WASM targets, the resolver is Send + Sync for thread-safe usage. #[cfg(not(target_arch = "wasm32"))] pub type AsyncResolver = dyn AsyncHttpResolver + Send + Sync; +/// Type alias for a trait object [`AsyncHttpResolver`] without Send + Sync bounds (WASM only). #[cfg(target_arch = "wasm32")] pub type AsyncResolver = dyn AsyncHttpResolver; From 1622bebb9046c2331c8a37636c10a32305622890 Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 12 Feb 2026 15:10:10 -0500 Subject: [PATCH 07/14] fix: use arc for sync resolver and use impls instead of trait objects --- sdk/src/builder.rs | 2 +- sdk/src/context.rs | 28 +++++++++++++++------------- sdk/src/cose_sign.rs | 9 +++++---- sdk/src/crypto/cose/sign.rs | 21 +++++++++++---------- sdk/src/crypto/cose/sigtst.rs | 5 +++-- sdk/src/http/interop.rs | 13 +++++++++++-- sdk/src/store.rs | 20 ++++++++++---------- 7 files changed, 56 insertions(+), 42 deletions(-) diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 893e7c5b1..6ef240ad4 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1864,7 +1864,7 @@ impl Builder { tsa_url, &manifest_label, &signature, - self.context().resolver(), + &self.context().resolver(), )?; } else { timestamp_assertion diff --git a/sdk/src/context.rs b/sdk/src/context.rs index 8ec7831a2..8cca220ad 100644 --- a/sdk/src/context.rs +++ b/sdk/src/context.rs @@ -16,7 +16,7 @@ use std::sync::{Arc, OnceLock}; use crate::{ http::{ restricted::RestrictedResolver, AsyncGenericResolver, AsyncHttpResolver, AsyncResolver, - BoxedSyncResolver, SyncGenericResolver, SyncHttpResolver, + SyncGenericResolver, SyncHttpResolver, SyncResolver, }, maybe_send_sync::{MaybeSend, MaybeSync}, settings::Settings, @@ -27,9 +27,9 @@ use crate::{ /// Internal state for sync HTTP resolver selection. enum SyncResolverState { /// User-provided custom resolver. - Custom(BoxedSyncResolver), + Custom(Arc), /// Default resolver with lazy initialization. - Default(OnceLock>), + Default(OnceLock>), } /// Internal state for async HTTP resolver selection. @@ -325,7 +325,7 @@ impl Context { mut self, resolver: T, ) -> Self { - self.sync_resolver = SyncResolverState::Custom(Box::new(resolver)); + self.sync_resolver = SyncResolverState::Custom(Arc::new(resolver)); self } @@ -333,7 +333,7 @@ impl Context { &mut self, resolver: T, ) -> Result<()> { - self.sync_resolver = SyncResolverState::Custom(Box::new(resolver)); + self.sync_resolver = SyncResolverState::Custom(Arc::new(resolver)); Ok(()) } @@ -364,15 +364,17 @@ impl Context { /// /// The default resolver is a `SyncGenericResolver` wrapped with `RestrictedResolver` /// to apply host filtering from the settings. - pub fn resolver(&self) -> &dyn SyncHttpResolver { + pub fn resolver(&self) -> Arc { match &self.sync_resolver { - SyncResolverState::Custom(resolver) => resolver.as_ref(), - SyncResolverState::Default(once_lock) => once_lock.get_or_init(|| { - let inner = SyncGenericResolver::new(); - let mut resolver = RestrictedResolver::new(inner); - resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone()); - resolver - }), + SyncResolverState::Custom(resolver) => resolver.clone(), + SyncResolverState::Default(once_lock) => once_lock + .get_or_init(|| { + let inner = SyncGenericResolver::new(); + let mut resolver = RestrictedResolver::new(inner); + resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone()); + Arc::new(resolver) + }) + .clone(), } } diff --git a/sdk/src/cose_sign.rs b/sdk/src/cose_sign.rs index af025d329..4fea1c1c2 100644 --- a/sdk/src/cose_sign.rs +++ b/sdk/src/cose_sign.rs @@ -30,6 +30,7 @@ use crate::{ time_stamp::{AsyncTimeStampProvider, TimeStampError, TimeStampProvider}, }, http::{AsyncHttpResolver, SyncHttpResolver}, + maybe_send_sync::MaybeSync, settings::Settings, status_tracker::{ErrorBehavior, StatusTracker}, AsyncSigner, Error, Result, Signer, @@ -57,14 +58,14 @@ use crate::{ signer: &dyn AsyncSigner, box_size: usize, settings: &Settings, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &(impl AsyncHttpResolver + MaybeSync), ))] pub fn sign_claim( claim_bytes: &[u8], signer: &dyn Signer, box_size: usize, settings: &Settings, - http_resolver: &dyn SyncHttpResolver, + http_resolver: &impl SyncHttpResolver, ) -> Result> { // Must be a valid claim. let label = "dummy_label"; @@ -120,7 +121,7 @@ pub fn sign_claim( box_size: usize, time_stamp_storage: TimeStampStorage, settings: &Settings, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &(impl AsyncHttpResolver + MaybeSync), ))] pub(crate) fn cose_sign( signer: &dyn Signer, @@ -128,7 +129,7 @@ pub(crate) fn cose_sign( box_size: usize, time_stamp_storage: TimeStampStorage, settings: &Settings, - http_resolver: &dyn SyncHttpResolver, + http_resolver: &impl SyncHttpResolver, ) -> Result> { // Make sure the signing cert is valid. let certs = signer.certs()?; diff --git a/sdk/src/crypto/cose/sign.rs b/sdk/src/crypto/cose/sign.rs index 87dcce7b3..d6bc8b9cc 100644 --- a/sdk/src/crypto/cose/sign.rs +++ b/sdk/src/crypto/cose/sign.rs @@ -30,6 +30,7 @@ use crate::{ raw_signature::{AsyncRawSigner, RawSigner, SigningAlg}, }, http::{AsyncHttpResolver, SyncHttpResolver}, + maybe_send_sync::MaybeSync, }; /// Given an arbitrary block of data and a [`RawSigner`] or [`AsyncRawSigner`] @@ -84,14 +85,14 @@ use crate::{ data: &[u8], box_size: Option, tss: TimeStampStorage, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &(impl AsyncHttpResolver + MaybeSync), ))] pub fn sign( signer: &dyn RawSigner, data: &[u8], box_size: Option, tss: TimeStampStorage, - http_resolver: &dyn SyncHttpResolver, + http_resolver: &impl SyncHttpResolver, ) -> Result, CoseError> { if _sync { match tss { @@ -115,14 +116,14 @@ pub fn sign( data: &[u8], box_size: Option, tss: TimeStampStorage, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &(impl AsyncHttpResolver + MaybeSync), ))] pub fn sign_v1( signer: &dyn RawSigner, data: &[u8], box_size: Option, tss: TimeStampStorage, - http_resolver: &dyn SyncHttpResolver, + http_resolver: &impl SyncHttpResolver, ) -> Result, CoseError> { let alg = signer.alg(); @@ -204,14 +205,14 @@ pub fn sign_v1( data: &[u8], box_size: Option, tss: TimeStampStorage, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &(impl AsyncHttpResolver + MaybeSync), ))] pub fn sign_v2( signer: &dyn RawSigner, data: &[u8], box_size: Option, tss: TimeStampStorage, - http_resolver: &dyn SyncHttpResolver, + http_resolver: &impl SyncHttpResolver, ) -> Result, CoseError> { if _sync { sign_v2_embedded( @@ -310,7 +311,7 @@ pub enum CosePayload { payload: CosePayload, content_type: Option, tss: TimeStampStorage, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &(impl AsyncHttpResolver + MaybeSync), ))] pub fn sign_v2_embedded( signer: &dyn RawSigner, @@ -319,7 +320,7 @@ pub fn sign_v2_embedded( payload: CosePayload, content_type: Option, tss: TimeStampStorage, - http_resolver: &dyn SyncHttpResolver, + http_resolver: &impl SyncHttpResolver, ) -> Result, CoseError> { let alg = signer.alg(); @@ -475,7 +476,7 @@ fn build_protected_header( data: &[u8], p_header: &ProtectedHeader, tss: TimeStampStorage, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &(impl AsyncHttpResolver + MaybeSync), )) ] fn build_unprotected_header( @@ -483,7 +484,7 @@ fn build_unprotected_header( data: &[u8], p_header: &ProtectedHeader, tss: TimeStampStorage, - http_resolver: &dyn SyncHttpResolver, + http_resolver: &impl SyncHttpResolver, ) -> Result { // signed_data_from_time_stamp_response diff --git a/sdk/src/crypto/cose/sigtst.rs b/sdk/src/crypto/cose/sigtst.rs index 9f00e5b8d..b7b9e50cb 100644 --- a/sdk/src/crypto/cose/sigtst.rs +++ b/sdk/src/crypto/cose/sigtst.rs @@ -32,6 +32,7 @@ use crate::{ }, http::{AsyncHttpResolver, SyncHttpResolver}, log_item, + maybe_send_sync::MaybeSync, status_tracker::StatusTracker, validation_status, Result, }; @@ -220,7 +221,7 @@ impl TstContainer { p_header: &ProtectedHeader, mut header_builder: HeaderBuilder, tss: TimeStampStorage, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &(impl AsyncHttpResolver + MaybeSync), )) ] pub(crate) fn add_sigtst_header( @@ -229,7 +230,7 @@ pub(crate) fn add_sigtst_header( p_header: &ProtectedHeader, mut header_builder: HeaderBuilder, tss: TimeStampStorage, - http_resolver: &dyn SyncHttpResolver, + http_resolver: &impl SyncHttpResolver, ) -> Result { let sd = cose_countersign_data(data, p_header); diff --git a/sdk/src/http/interop.rs b/sdk/src/http/interop.rs index a7570e559..66296af62 100644 --- a/sdk/src/http/interop.rs +++ b/sdk/src/http/interop.rs @@ -26,11 +26,11 @@ use crate::http::{AsyncHttpResolver, HttpResolverError, SyncHttpResolver}; /// Type alias for a boxed [`SyncHttpResolver`] with conditional Send + Sync bounds. /// On non-WASM targets, the resolver is Send + Sync for thread-safe usage. #[cfg(not(target_arch = "wasm32"))] -pub type BoxedSyncResolver = Box; +pub type SyncResolver = dyn SyncHttpResolver + Send + Sync; /// Type alias for a boxed [`SyncHttpResolver`] without Send + Sync bounds (WASM only). #[cfg(target_arch = "wasm32")] -pub type BoxedSyncResolver = Box; +pub type SyncResolver = dyn SyncHttpResolver; /// Type alias for a trait object [`AsyncHttpResolver`] with Send + Sync bounds. /// On non-WASM targets, the resolver is Send + Sync for thread-safe usage. @@ -41,6 +41,15 @@ pub type AsyncResolver = dyn AsyncHttpResolver + Send + Sync; #[cfg(target_arch = "wasm32")] pub type AsyncResolver = dyn AsyncHttpResolver; +impl SyncHttpResolver for Arc { + fn http_resolve( + &self, + request: Request>, + ) -> Result>, HttpResolverError> { + (**self).http_resolve(request) + } +} + #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] impl AsyncHttpResolver for Arc { diff --git a/sdk/src/store.rs b/sdk/src/store.rs index ab93dba77..dee732a17 100644 --- a/sdk/src/store.rs +++ b/sdk/src/store.rs @@ -74,7 +74,7 @@ use crate::{ }, log_item, manifest_store_report::ManifestStoreReport, - maybe_send_sync::MaybeSend, + maybe_send_sync::{MaybeSend, MaybeSync}, settings::{builder::OcspFetchScope, Settings}, status_tracker::{ErrorBehavior, StatusTracker}, utils::{ @@ -549,7 +549,7 @@ impl Store { signer: &dyn AsyncSigner, box_size: usize, settings: &Settings, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &(impl AsyncHttpResolver + MaybeSync), ))] pub fn sign_claim( &self, @@ -557,7 +557,7 @@ impl Store { signer: &dyn Signer, box_size: usize, settings: &Settings, - http_resolver: &dyn SyncHttpResolver, + http_resolver: &impl SyncHttpResolver, ) -> Result> { let claim_bytes = claim.data()?; @@ -2562,7 +2562,7 @@ impl Store { signer, signer.reserve_size(), context.settings(), - context.resolver(), + &context.resolver(), )?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); @@ -2600,7 +2600,7 @@ impl Store { signer, signer.reserve_size(), context.settings(), - context.resolver_async().as_ref(), + &context.resolver_async(), ) .await?; @@ -2638,7 +2638,7 @@ impl Store { signer, signer.reserve_size(), context.settings(), - context.resolver(), + &context.resolver(), )?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); @@ -2682,7 +2682,7 @@ impl Store { signer, signer.reserve_size(), context.settings(), - context.resolver_async().as_ref(), + &context.resolver_async(), ) .await?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); @@ -2956,7 +2956,7 @@ impl Store { signer, signer.reserve_size(), context.settings(), - context.resolver(), + &context.resolver(), )?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); @@ -3072,7 +3072,7 @@ impl Store { signer, signer.reserve_size(), settings, - context.resolver(), + &context.resolver(), ) } else { self.sign_claim_async( @@ -3080,7 +3080,7 @@ impl Store { signer, signer.reserve_size(), settings, - context.resolver_async().as_ref(), + &context.resolver_async(), ) .await }?; From 59dd42c0dfa8f01b64e5632dc28ccfd91bb754eb Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 12 Feb 2026 15:21:53 -0500 Subject: [PATCH 08/14] refactor: require MaybeSend and MaybeSync on the core HTTP resolver traits --- sdk/src/context.rs | 16 ++--- sdk/src/cose_sign.rs | 7 +-- sdk/src/crypto/cose/sign.rs | 11 ++-- sdk/src/crypto/cose/sigtst.rs | 3 +- sdk/src/crypto/raw_signature/signer.rs | 2 +- sdk/src/crypto/time_stamp/provider.rs | 2 +- sdk/src/http/interop.rs | 62 ------------------- sdk/src/http/mod.rs | 59 ++++++++++++++++-- .../async_identity_assertion_signer.rs | 2 +- sdk/src/signer.rs | 4 +- sdk/src/store.rs | 4 +- sdk/src/utils/test.rs | 2 +- sdk/src/utils/test_signer.rs | 2 +- 13 files changed, 79 insertions(+), 97 deletions(-) delete mode 100644 sdk/src/http/interop.rs diff --git a/sdk/src/context.rs b/sdk/src/context.rs index 8cca220ad..fc607df71 100644 --- a/sdk/src/context.rs +++ b/sdk/src/context.rs @@ -15,8 +15,8 @@ use std::sync::{Arc, OnceLock}; use crate::{ http::{ - restricted::RestrictedResolver, AsyncGenericResolver, AsyncHttpResolver, AsyncResolver, - SyncGenericResolver, SyncHttpResolver, SyncResolver, + restricted::RestrictedResolver, AsyncGenericResolver, AsyncHttpResolver, + SyncGenericResolver, SyncHttpResolver, }, maybe_send_sync::{MaybeSend, MaybeSync}, settings::Settings, @@ -27,17 +27,17 @@ use crate::{ /// Internal state for sync HTTP resolver selection. enum SyncResolverState { /// User-provided custom resolver. - Custom(Arc), + Custom(Arc), /// Default resolver with lazy initialization. - Default(OnceLock>), + Default(OnceLock>), } /// Internal state for async HTTP resolver selection. enum AsyncResolverState { /// User-provided custom resolver. - Custom(Arc), + Custom(Arc), /// Default resolver with lazy initialization. - Default(OnceLock>), + Default(OnceLock>), } /// Internal state for signer selection. @@ -364,7 +364,7 @@ impl Context { /// /// The default resolver is a `SyncGenericResolver` wrapped with `RestrictedResolver` /// to apply host filtering from the settings. - pub fn resolver(&self) -> Arc { + pub fn resolver(&self) -> Arc { match &self.sync_resolver { SyncResolverState::Custom(resolver) => resolver.clone(), SyncResolverState::Default(once_lock) => once_lock @@ -382,7 +382,7 @@ impl Context { /// /// The default resolver is an `AsyncGenericResolver` wrapped with `RestrictedResolver` /// to apply host filtering from the settings. - pub fn resolver_async(&self) -> Arc { + pub fn resolver_async(&self) -> Arc { match &self.async_resolver { AsyncResolverState::Custom(resolver) => resolver.clone(), AsyncResolverState::Default(once_lock) => once_lock diff --git a/sdk/src/cose_sign.rs b/sdk/src/cose_sign.rs index 4fea1c1c2..536ca8231 100644 --- a/sdk/src/cose_sign.rs +++ b/sdk/src/cose_sign.rs @@ -30,7 +30,6 @@ use crate::{ time_stamp::{AsyncTimeStampProvider, TimeStampError, TimeStampProvider}, }, http::{AsyncHttpResolver, SyncHttpResolver}, - maybe_send_sync::MaybeSync, settings::Settings, status_tracker::{ErrorBehavior, StatusTracker}, AsyncSigner, Error, Result, Signer, @@ -58,7 +57,7 @@ use crate::{ signer: &dyn AsyncSigner, box_size: usize, settings: &Settings, - http_resolver: &(impl AsyncHttpResolver + MaybeSync), + http_resolver: &impl AsyncHttpResolver, ))] pub fn sign_claim( claim_bytes: &[u8], @@ -121,7 +120,7 @@ pub fn sign_claim( box_size: usize, time_stamp_storage: TimeStampStorage, settings: &Settings, - http_resolver: &(impl AsyncHttpResolver + MaybeSync), + http_resolver: &impl AsyncHttpResolver, ))] pub(crate) fn cose_sign( signer: &dyn Signer, @@ -304,7 +303,7 @@ impl AsyncTimeStampProvider for AsyncSignerWrapper<'_> { async fn send_time_stamp_request( &self, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &dyn AsyncHttpResolver, message: &[u8], ) -> Option, TimeStampError>> { self.0 diff --git a/sdk/src/crypto/cose/sign.rs b/sdk/src/crypto/cose/sign.rs index d6bc8b9cc..b9f190a42 100644 --- a/sdk/src/crypto/cose/sign.rs +++ b/sdk/src/crypto/cose/sign.rs @@ -30,7 +30,6 @@ use crate::{ raw_signature::{AsyncRawSigner, RawSigner, SigningAlg}, }, http::{AsyncHttpResolver, SyncHttpResolver}, - maybe_send_sync::MaybeSync, }; /// Given an arbitrary block of data and a [`RawSigner`] or [`AsyncRawSigner`] @@ -85,7 +84,7 @@ use crate::{ data: &[u8], box_size: Option, tss: TimeStampStorage, - http_resolver: &(impl AsyncHttpResolver + MaybeSync), + http_resolver: &impl AsyncHttpResolver, ))] pub fn sign( signer: &dyn RawSigner, @@ -116,7 +115,7 @@ pub fn sign( data: &[u8], box_size: Option, tss: TimeStampStorage, - http_resolver: &(impl AsyncHttpResolver + MaybeSync), + http_resolver: &impl AsyncHttpResolver, ))] pub fn sign_v1( signer: &dyn RawSigner, @@ -205,7 +204,7 @@ pub fn sign_v1( data: &[u8], box_size: Option, tss: TimeStampStorage, - http_resolver: &(impl AsyncHttpResolver + MaybeSync), + http_resolver: &impl AsyncHttpResolver, ))] pub fn sign_v2( signer: &dyn RawSigner, @@ -311,7 +310,7 @@ pub enum CosePayload { payload: CosePayload, content_type: Option, tss: TimeStampStorage, - http_resolver: &(impl AsyncHttpResolver + MaybeSync), + http_resolver: &impl AsyncHttpResolver, ))] pub fn sign_v2_embedded( signer: &dyn RawSigner, @@ -476,7 +475,7 @@ fn build_protected_header( data: &[u8], p_header: &ProtectedHeader, tss: TimeStampStorage, - http_resolver: &(impl AsyncHttpResolver + MaybeSync), + http_resolver: &impl AsyncHttpResolver, )) ] fn build_unprotected_header( diff --git a/sdk/src/crypto/cose/sigtst.rs b/sdk/src/crypto/cose/sigtst.rs index b7b9e50cb..f2092208a 100644 --- a/sdk/src/crypto/cose/sigtst.rs +++ b/sdk/src/crypto/cose/sigtst.rs @@ -32,7 +32,6 @@ use crate::{ }, http::{AsyncHttpResolver, SyncHttpResolver}, log_item, - maybe_send_sync::MaybeSync, status_tracker::StatusTracker, validation_status, Result, }; @@ -221,7 +220,7 @@ impl TstContainer { p_header: &ProtectedHeader, mut header_builder: HeaderBuilder, tss: TimeStampStorage, - http_resolver: &(impl AsyncHttpResolver + MaybeSync), + http_resolver: &impl AsyncHttpResolver, )) ] pub(crate) fn add_sigtst_header( diff --git a/sdk/src/crypto/raw_signature/signer.rs b/sdk/src/crypto/raw_signature/signer.rs index 5056a7ca6..81753e201 100644 --- a/sdk/src/crypto/raw_signature/signer.rs +++ b/sdk/src/crypto/raw_signature/signer.rs @@ -273,7 +273,7 @@ impl AsyncTimeStampProvider for AsyncRawSignerWrapper { // implement its own timestamping code anyways(?) because of that I thikn we can remove most of this trait impl // async fn send_time_stamp_request( // &self, - // http_resolver: &(dyn AsyncHttpResolver + Sync), + // http_resolver: &dyn AsyncHttpResolver, // message: &[u8], // ) -> Option, TimeStampError>> { // self.0.send_time_stamp_request(message) diff --git a/sdk/src/crypto/time_stamp/provider.rs b/sdk/src/crypto/time_stamp/provider.rs index 8adddebf0..82c57b35c 100644 --- a/sdk/src/crypto/time_stamp/provider.rs +++ b/sdk/src/crypto/time_stamp/provider.rs @@ -119,7 +119,7 @@ pub trait AsyncTimeStampProvider: MaybeSync { /// [RFC 3161]: https://datatracker.ietf.org/doc/html/rfc3161 async fn send_time_stamp_request( &self, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &dyn AsyncHttpResolver, message: &[u8], ) -> Option, TimeStampError>> { if let Some(url) = self.time_stamp_service_url() { diff --git a/sdk/src/http/interop.rs b/sdk/src/http/interop.rs deleted file mode 100644 index 66296af62..000000000 --- a/sdk/src/http/interop.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2026 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, -// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -// or the MIT license (http://opensource.org/licenses/MIT), -// at your option. - -// Unless required by applicable law or agreed to in writing, -// this software is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or -// implied. See the LICENSE-MIT and LICENSE-APACHE files for the -// specific language governing permissions and limitations under -// each license. - -//! Type aliases and traits for boxed and arced HTTP resolvers with conditional Send + Sync bounds. -//! These are particularly useful when dealing with trait objects, such as in the case of [`Context`]. -//! -//! [`Context`]: crate::Context - -use std::{io::Read, sync::Arc}; - -use async_trait::async_trait; -use http::{Request, Response}; - -use crate::http::{AsyncHttpResolver, HttpResolverError, SyncHttpResolver}; - -/// Type alias for a boxed [`SyncHttpResolver`] with conditional Send + Sync bounds. -/// On non-WASM targets, the resolver is Send + Sync for thread-safe usage. -#[cfg(not(target_arch = "wasm32"))] -pub type SyncResolver = dyn SyncHttpResolver + Send + Sync; - -/// Type alias for a boxed [`SyncHttpResolver`] without Send + Sync bounds (WASM only). -#[cfg(target_arch = "wasm32")] -pub type SyncResolver = dyn SyncHttpResolver; - -/// Type alias for a trait object [`AsyncHttpResolver`] with Send + Sync bounds. -/// On non-WASM targets, the resolver is Send + Sync for thread-safe usage. -#[cfg(not(target_arch = "wasm32"))] -pub type AsyncResolver = dyn AsyncHttpResolver + Send + Sync; - -/// Type alias for a trait object [`AsyncHttpResolver`] without Send + Sync bounds (WASM only). -#[cfg(target_arch = "wasm32")] -pub type AsyncResolver = dyn AsyncHttpResolver; - -impl SyncHttpResolver for Arc { - fn http_resolve( - &self, - request: Request>, - ) -> Result>, HttpResolverError> { - (**self).http_resolve(request) - } -} - -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -impl AsyncHttpResolver for Arc { - async fn http_resolve_async( - &self, - request: Request>, - ) -> Result>, HttpResolverError> { - (**self).http_resolve_async(request).await - } -} diff --git a/sdk/src/http/mod.rs b/sdk/src/http/mod.rs index eebeafa82..52f8c803d 100644 --- a/sdk/src/http/mod.rs +++ b/sdk/src/http/mod.rs @@ -41,14 +41,19 @@ //! [`CertificateStatus`]: crate::assertions::CertificateStatus //! [`SignerSettings::Remote`]: crate::settings::signer::SignerSettings::Remote -use std::io::{self, Read}; +use std::{ + io::{self, Read}, + sync::Arc, +}; use async_trait::async_trait; use http::{Request, Response}; -use crate::Result; +use crate::{ + maybe_send_sync::{MaybeSend, MaybeSync}, + Result, +}; -mod interop; mod reqwest; mod ureq; mod wasi; @@ -57,10 +62,15 @@ pub mod restricted; // Since we expose `http::Request` and `http::Response` in the public API, we also expose the `http` crate. pub use http; -pub use interop::*; /// A resolver for sync (blocking) HTTP requests. -pub trait SyncHttpResolver { +/// +/// This trait is a supertrait of [`MaybeSend`] and [`MaybeSync`] for consistency with the +/// [`AsyncHttpResolver`]. For more information on the rationale, see [`AsyncHttpResolver`]. +/// +/// [`MaybeSend`]: crate::maybe_send_sync::MaybeSend +/// [`MaybeSync`]: crate::maybe_send_sync::MaybeSync +pub trait SyncHttpResolver: MaybeSend + MaybeSync { /// Resolve a [`Request`] into a [`Response`] with a streaming body. /// /// [`Request`]: http::Request @@ -71,10 +81,32 @@ pub trait SyncHttpResolver { ) -> Result>, HttpResolverError>; } +/// This implementation is particularly useful for compatibility with the return +/// type of [`Context::sync_resolver`]. +/// +/// [`Context::sync_resolver`]: crate::Context::sync_resolver +impl SyncHttpResolver for Arc { + fn http_resolve( + &self, + request: Request>, + ) -> Result>, HttpResolverError> { + (**self).http_resolve(request) + } +} + /// A resolver for non-blocking (async) HTTP requests. +/// +/// This trait is a supertrait of [`MaybeSend`] and [`MaybeSync`] because in many cases +/// we use the pattern `&dyn AsyncHttpResolver`. For that to cross an await point, it +/// must implement `Send`, and for that to happen, it must also implement `Sync`. Thus, +/// rather than creating a new trait that combines `AsyncHttpResolver + MaybeSend + MaybeSync`, +/// we require it here to reduce complexity. +/// +/// [`MaybeSend`]: crate::maybe_send_sync::MaybeSend +/// [`MaybeSync`]: crate::maybe_send_sync::MaybeSync #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -pub trait AsyncHttpResolver { +pub trait AsyncHttpResolver: MaybeSend + MaybeSync { /// Resolve a [`Request`] into a [`Response`] with a streaming body. /// /// [`Request`]: http::Request @@ -85,6 +117,21 @@ pub trait AsyncHttpResolver { ) -> Result>, HttpResolverError>; } +/// This implementation is particularly useful for compatibility with the return +/// type of [`Context::async_resolver`]. +/// +/// [`Context::async_resolver`]: crate::Context::async_resolver +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +impl AsyncHttpResolver for Arc { + async fn http_resolve_async( + &self, + request: Request>, + ) -> Result>, HttpResolverError> { + (**self).http_resolve_async(request).await + } +} + /// A generic resolver for [`SyncHttpResolver`]. /// /// This implementation will automatically choose a [`SyncHttpResolver`] based on the diff --git a/sdk/src/identity/builder/async_identity_assertion_signer.rs b/sdk/src/identity/builder/async_identity_assertion_signer.rs index 6352701ba..317756cbe 100644 --- a/sdk/src/identity/builder/async_identity_assertion_signer.rs +++ b/sdk/src/identity/builder/async_identity_assertion_signer.rs @@ -137,7 +137,7 @@ impl AsyncSigner for AsyncIdentityAssertionSigner { async fn send_timestamp_request( &self, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &dyn AsyncHttpResolver, message: &[u8], ) -> Option>> { self.signer diff --git a/sdk/src/signer.rs b/sdk/src/signer.rs index 4ebd99736..17665b1c8 100644 --- a/sdk/src/signer.rs +++ b/sdk/src/signer.rs @@ -222,7 +222,7 @@ pub trait AsyncSigner: MaybeSend + MaybeSync { /// provided by [`Self::time_authority_url()`], if any. async fn send_timestamp_request( &self, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &dyn AsyncHttpResolver, message: &[u8], ) -> Option>> { if let Some(url) = self.time_authority_url() { @@ -423,7 +423,7 @@ impl AsyncSigner for Box { async fn send_timestamp_request( &self, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &dyn AsyncHttpResolver, message: &[u8], ) -> Option>> { (**self) diff --git a/sdk/src/store.rs b/sdk/src/store.rs index dee732a17..3e2210a1e 100644 --- a/sdk/src/store.rs +++ b/sdk/src/store.rs @@ -74,7 +74,7 @@ use crate::{ }, log_item, manifest_store_report::ManifestStoreReport, - maybe_send_sync::{MaybeSend, MaybeSync}, + maybe_send_sync::MaybeSend, settings::{builder::OcspFetchScope, Settings}, status_tracker::{ErrorBehavior, StatusTracker}, utils::{ @@ -549,7 +549,7 @@ impl Store { signer: &dyn AsyncSigner, box_size: usize, settings: &Settings, - http_resolver: &(impl AsyncHttpResolver + MaybeSync), + http_resolver: &impl AsyncHttpResolver, ))] pub fn sign_claim( &self, diff --git a/sdk/src/utils/test.rs b/sdk/src/utils/test.rs index 3c10c1f28..7907f47cc 100644 --- a/sdk/src/utils/test.rs +++ b/sdk/src/utils/test.rs @@ -739,7 +739,7 @@ impl AsyncSigner for AsyncTestGoodSigner { async fn send_timestamp_request( &self, - _http_resolver: &(dyn AsyncHttpResolver + Sync), + _http_resolver: &dyn AsyncHttpResolver, _message: &[u8], ) -> Option>> { Some(Ok(Vec::new())) diff --git a/sdk/src/utils/test_signer.rs b/sdk/src/utils/test_signer.rs index 53e838080..5bbb6be06 100644 --- a/sdk/src/utils/test_signer.rs +++ b/sdk/src/utils/test_signer.rs @@ -158,7 +158,7 @@ impl AsyncSigner for AsyncRawSignerWrapper { async fn send_timestamp_request( &self, - http_resolver: &(dyn AsyncHttpResolver + Sync), + http_resolver: &dyn AsyncHttpResolver, message: &[u8], ) -> Option>> { self.0 From 0b2fcdf372639cf193745acb1e68d60c4a914603 Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 12 Feb 2026 15:24:59 -0500 Subject: [PATCH 09/14] refactor: simplify by requiring MaybeSend and MaybeSync bounds on HTTP resolver traits --- sdk/src/assertions/timestamp.rs | 4 +-- sdk/src/builder.rs | 2 +- sdk/src/context.rs | 36 ++++++++++---------- sdk/src/http/interop.rs | 53 ----------------------------- sdk/src/http/mod.rs | 59 +++++++++++++++++++++++++++++---- 5 files changed, 75 insertions(+), 79 deletions(-) delete mode 100644 sdk/src/http/interop.rs diff --git a/sdk/src/assertions/timestamp.rs b/sdk/src/assertions/timestamp.rs index e80a74c74..690e84e2d 100644 --- a/sdk/src/assertions/timestamp.rs +++ b/sdk/src/assertions/timestamp.rs @@ -75,7 +75,7 @@ impl TimeStamp { tsa_url: &str, manifest_id: &str, signature: &[u8], - http_resolver: &(impl SyncHttpResolver + ?Sized), + http_resolver: &impl SyncHttpResolver, ) -> Result<()> { let timestamp_token = if _sync { TimeStamp::send_timestamp_token_request(tsa_url, signature, http_resolver)? @@ -102,7 +102,7 @@ impl TimeStamp { pub(crate) fn send_timestamp_token_request( tsa_url: &str, message: &[u8], - http_resolver: &(impl SyncHttpResolver + ?Sized), + http_resolver: &impl SyncHttpResolver, ) -> Result> { let body = crate::crypto::time_stamp::default_rfc3161_message(message)?; let headers = None; diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 893e7c5b1..6ef240ad4 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1864,7 +1864,7 @@ impl Builder { tsa_url, &manifest_label, &signature, - self.context().resolver(), + &self.context().resolver(), )?; } else { timestamp_assertion diff --git a/sdk/src/context.rs b/sdk/src/context.rs index 8ec7831a2..fc607df71 100644 --- a/sdk/src/context.rs +++ b/sdk/src/context.rs @@ -15,8 +15,8 @@ use std::sync::{Arc, OnceLock}; use crate::{ http::{ - restricted::RestrictedResolver, AsyncGenericResolver, AsyncHttpResolver, AsyncResolver, - BoxedSyncResolver, SyncGenericResolver, SyncHttpResolver, + restricted::RestrictedResolver, AsyncGenericResolver, AsyncHttpResolver, + SyncGenericResolver, SyncHttpResolver, }, maybe_send_sync::{MaybeSend, MaybeSync}, settings::Settings, @@ -27,17 +27,17 @@ use crate::{ /// Internal state for sync HTTP resolver selection. enum SyncResolverState { /// User-provided custom resolver. - Custom(BoxedSyncResolver), + Custom(Arc), /// Default resolver with lazy initialization. - Default(OnceLock>), + Default(OnceLock>), } /// Internal state for async HTTP resolver selection. enum AsyncResolverState { /// User-provided custom resolver. - Custom(Arc), + Custom(Arc), /// Default resolver with lazy initialization. - Default(OnceLock>), + Default(OnceLock>), } /// Internal state for signer selection. @@ -325,7 +325,7 @@ impl Context { mut self, resolver: T, ) -> Self { - self.sync_resolver = SyncResolverState::Custom(Box::new(resolver)); + self.sync_resolver = SyncResolverState::Custom(Arc::new(resolver)); self } @@ -333,7 +333,7 @@ impl Context { &mut self, resolver: T, ) -> Result<()> { - self.sync_resolver = SyncResolverState::Custom(Box::new(resolver)); + self.sync_resolver = SyncResolverState::Custom(Arc::new(resolver)); Ok(()) } @@ -364,15 +364,17 @@ impl Context { /// /// The default resolver is a `SyncGenericResolver` wrapped with `RestrictedResolver` /// to apply host filtering from the settings. - pub fn resolver(&self) -> &dyn SyncHttpResolver { + pub fn resolver(&self) -> Arc { match &self.sync_resolver { - SyncResolverState::Custom(resolver) => resolver.as_ref(), - SyncResolverState::Default(once_lock) => once_lock.get_or_init(|| { - let inner = SyncGenericResolver::new(); - let mut resolver = RestrictedResolver::new(inner); - resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone()); - resolver - }), + SyncResolverState::Custom(resolver) => resolver.clone(), + SyncResolverState::Default(once_lock) => once_lock + .get_or_init(|| { + let inner = SyncGenericResolver::new(); + let mut resolver = RestrictedResolver::new(inner); + resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone()); + Arc::new(resolver) + }) + .clone(), } } @@ -380,7 +382,7 @@ impl Context { /// /// The default resolver is an `AsyncGenericResolver` wrapped with `RestrictedResolver` /// to apply host filtering from the settings. - pub fn resolver_async(&self) -> Arc { + pub fn resolver_async(&self) -> Arc { match &self.async_resolver { AsyncResolverState::Custom(resolver) => resolver.clone(), AsyncResolverState::Default(once_lock) => once_lock diff --git a/sdk/src/http/interop.rs b/sdk/src/http/interop.rs deleted file mode 100644 index a7570e559..000000000 --- a/sdk/src/http/interop.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2026 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, -// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -// or the MIT license (http://opensource.org/licenses/MIT), -// at your option. - -// Unless required by applicable law or agreed to in writing, -// this software is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or -// implied. See the LICENSE-MIT and LICENSE-APACHE files for the -// specific language governing permissions and limitations under -// each license. - -//! Type aliases and traits for boxed and arced HTTP resolvers with conditional Send + Sync bounds. -//! These are particularly useful when dealing with trait objects, such as in the case of [`Context`]. -//! -//! [`Context`]: crate::Context - -use std::{io::Read, sync::Arc}; - -use async_trait::async_trait; -use http::{Request, Response}; - -use crate::http::{AsyncHttpResolver, HttpResolverError, SyncHttpResolver}; - -/// Type alias for a boxed [`SyncHttpResolver`] with conditional Send + Sync bounds. -/// On non-WASM targets, the resolver is Send + Sync for thread-safe usage. -#[cfg(not(target_arch = "wasm32"))] -pub type BoxedSyncResolver = Box; - -/// Type alias for a boxed [`SyncHttpResolver`] without Send + Sync bounds (WASM only). -#[cfg(target_arch = "wasm32")] -pub type BoxedSyncResolver = Box; - -/// Type alias for a trait object [`AsyncHttpResolver`] with Send + Sync bounds. -/// On non-WASM targets, the resolver is Send + Sync for thread-safe usage. -#[cfg(not(target_arch = "wasm32"))] -pub type AsyncResolver = dyn AsyncHttpResolver + Send + Sync; - -/// Type alias for a trait object [`AsyncHttpResolver`] without Send + Sync bounds (WASM only). -#[cfg(target_arch = "wasm32")] -pub type AsyncResolver = dyn AsyncHttpResolver; - -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -impl AsyncHttpResolver for Arc { - async fn http_resolve_async( - &self, - request: Request>, - ) -> Result>, HttpResolverError> { - (**self).http_resolve_async(request).await - } -} diff --git a/sdk/src/http/mod.rs b/sdk/src/http/mod.rs index eebeafa82..52f8c803d 100644 --- a/sdk/src/http/mod.rs +++ b/sdk/src/http/mod.rs @@ -41,14 +41,19 @@ //! [`CertificateStatus`]: crate::assertions::CertificateStatus //! [`SignerSettings::Remote`]: crate::settings::signer::SignerSettings::Remote -use std::io::{self, Read}; +use std::{ + io::{self, Read}, + sync::Arc, +}; use async_trait::async_trait; use http::{Request, Response}; -use crate::Result; +use crate::{ + maybe_send_sync::{MaybeSend, MaybeSync}, + Result, +}; -mod interop; mod reqwest; mod ureq; mod wasi; @@ -57,10 +62,15 @@ pub mod restricted; // Since we expose `http::Request` and `http::Response` in the public API, we also expose the `http` crate. pub use http; -pub use interop::*; /// A resolver for sync (blocking) HTTP requests. -pub trait SyncHttpResolver { +/// +/// This trait is a supertrait of [`MaybeSend`] and [`MaybeSync`] for consistency with the +/// [`AsyncHttpResolver`]. For more information on the rationale, see [`AsyncHttpResolver`]. +/// +/// [`MaybeSend`]: crate::maybe_send_sync::MaybeSend +/// [`MaybeSync`]: crate::maybe_send_sync::MaybeSync +pub trait SyncHttpResolver: MaybeSend + MaybeSync { /// Resolve a [`Request`] into a [`Response`] with a streaming body. /// /// [`Request`]: http::Request @@ -71,10 +81,32 @@ pub trait SyncHttpResolver { ) -> Result>, HttpResolverError>; } +/// This implementation is particularly useful for compatibility with the return +/// type of [`Context::sync_resolver`]. +/// +/// [`Context::sync_resolver`]: crate::Context::sync_resolver +impl SyncHttpResolver for Arc { + fn http_resolve( + &self, + request: Request>, + ) -> Result>, HttpResolverError> { + (**self).http_resolve(request) + } +} + /// A resolver for non-blocking (async) HTTP requests. +/// +/// This trait is a supertrait of [`MaybeSend`] and [`MaybeSync`] because in many cases +/// we use the pattern `&dyn AsyncHttpResolver`. For that to cross an await point, it +/// must implement `Send`, and for that to happen, it must also implement `Sync`. Thus, +/// rather than creating a new trait that combines `AsyncHttpResolver + MaybeSend + MaybeSync`, +/// we require it here to reduce complexity. +/// +/// [`MaybeSend`]: crate::maybe_send_sync::MaybeSend +/// [`MaybeSync`]: crate::maybe_send_sync::MaybeSync #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -pub trait AsyncHttpResolver { +pub trait AsyncHttpResolver: MaybeSend + MaybeSync { /// Resolve a [`Request`] into a [`Response`] with a streaming body. /// /// [`Request`]: http::Request @@ -85,6 +117,21 @@ pub trait AsyncHttpResolver { ) -> Result>, HttpResolverError>; } +/// This implementation is particularly useful for compatibility with the return +/// type of [`Context::async_resolver`]. +/// +/// [`Context::async_resolver`]: crate::Context::async_resolver +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +impl AsyncHttpResolver for Arc { + async fn http_resolve_async( + &self, + request: Request>, + ) -> Result>, HttpResolverError> { + (**self).http_resolve_async(request).await + } +} + /// A generic resolver for [`SyncHttpResolver`]. /// /// This implementation will automatically choose a [`SyncHttpResolver`] based on the From deb378d8aa98aa972e9a36ce231c3e473a63fbdf Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 12 Feb 2026 15:38:42 -0500 Subject: [PATCH 10/14] fix: new code to use http resolvers in send_timestamp_request --- sdk/src/utils/ephemeral_signer.rs | 53 ++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/sdk/src/utils/ephemeral_signer.rs b/sdk/src/utils/ephemeral_signer.rs index a863b183c..847b41563 100644 --- a/sdk/src/utils/ephemeral_signer.rs +++ b/sdk/src/utils/ephemeral_signer.rs @@ -28,6 +28,7 @@ use x509_parser::prelude::X509Certificate; use crate::{ crypto::raw_signature::{signer_from_cert_chain_and_private_key, RawSigner, SigningAlg}, + http::SyncHttpResolver, Error, Result, Signer, }; @@ -230,9 +231,13 @@ impl Signer for EphemeralSigner { .map_err(|e| e.into()) } - fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + fn send_timestamp_request( + &self, + http_resolver: &dyn SyncHttpResolver, + message: &[u8], + ) -> Option>> { self.raw_signer - .send_time_stamp_request(message) + .send_time_stamp_request(http_resolver, message) .map(|r| r.map_err(|e| e.into())) } } @@ -252,6 +257,7 @@ mod tests { crypto::cose::{ cert_chain_from_sign1, parse_cose_sign1, CertificateTrustPolicy, TimeStampStorage, }, + http::SyncGenericResolver, settings::Settings, status_tracker::StatusTracker, Signer, @@ -273,8 +279,15 @@ mod tests { let tss = TimeStampStorage::V1_sigTst; - let cose_bytes = cose_sign(&signer, &claim_bytes, signer.reserve_size(), tss, &settings) - .expect("cose_sign with EphemeralSigner"); + let cose_bytes = cose_sign( + &signer, + &claim_bytes, + signer.reserve_size(), + tss, + &settings, + &SyncGenericResolver::new(), + ) + .expect("cose_sign with EphemeralSigner"); let mut log = StatusTracker::default(); let sign1 = parse_cose_sign1(&cose_bytes, &claim_bytes, &mut log) @@ -310,8 +323,15 @@ mod tests { let tss = TimeStampStorage::V1_sigTst; - let cose_bytes = cose_sign(&signer, &claim_bytes, signer.reserve_size(), tss, &settings) - .expect("cose_sign with EphemeralSigner"); + let cose_bytes = cose_sign( + &signer, + &claim_bytes, + signer.reserve_size(), + tss, + &settings, + &SyncGenericResolver::new(), + ) + .expect("cose_sign with EphemeralSigner"); let mut log = StatusTracker::default(); let sign1 = parse_cose_sign1(&cose_bytes, &claim_bytes, &mut log).expect("parse COSE"); @@ -351,8 +371,14 @@ mod tests { let mut settings = Settings::default(); settings.verify.verify_trust = false; - let cose_bytes = sign_claim(&claim_bytes, &signer, signer.reserve_size(), &settings) - .expect("sign_claim with EphemeralSigner"); + let cose_bytes = sign_claim( + &claim_bytes, + &signer, + signer.reserve_size(), + &settings, + &SyncGenericResolver::new(), + ) + .expect("sign_claim with EphemeralSigner"); let mut validation_log = StatusTracker::default(); let ctp = CertificateTrustPolicy::default(); @@ -395,9 +421,14 @@ mod tests { let mut settings = Settings::default(); settings.verify.verify_trust = false; - let cose_bytes = - crate::cose_sign::sign_claim(&claim_bytes, &signer, signer.reserve_size(), &settings) - .expect("sign_claim with EphemeralSigner"); + let cose_bytes = crate::cose_sign::sign_claim( + &claim_bytes, + &signer, + signer.reserve_size(), + &settings, + &SyncGenericResolver::new(), + ) + .expect("sign_claim with EphemeralSigner"); let mut validation_log = StatusTracker::default(); From 40eb0ca893103563682bcac70db5c539f231324e Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 12 Feb 2026 15:47:27 -0500 Subject: [PATCH 11/14] docs: fix doc links --- sdk/src/http/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/sdk/src/http/mod.rs b/sdk/src/http/mod.rs index 52f8c803d..310f57dac 100644 --- a/sdk/src/http/mod.rs +++ b/sdk/src/http/mod.rs @@ -82,9 +82,9 @@ pub trait SyncHttpResolver: MaybeSend + MaybeSync { } /// This implementation is particularly useful for compatibility with the return -/// type of [`Context::sync_resolver`]. +/// type of [`Context::resolver`]. /// -/// [`Context::sync_resolver`]: crate::Context::sync_resolver +/// [`Context::resolver`]: crate::Context::resolver impl SyncHttpResolver for Arc { fn http_resolve( &self, @@ -118,9 +118,9 @@ pub trait AsyncHttpResolver: MaybeSend + MaybeSync { } /// This implementation is particularly useful for compatibility with the return -/// type of [`Context::async_resolver`]. +/// type of [`Context::resolver_async`]. /// -/// [`Context::async_resolver`]: crate::Context::async_resolver +/// [`Context::resolver_async`]: crate::Context::resolver_async #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] impl AsyncHttpResolver for Arc { @@ -153,7 +153,7 @@ pub struct SyncGenericResolver { impl SyncGenericResolver { /// Create a new [`SyncGenericResolver`] with an auto-specified [`SyncHttpResolver`]. /// - /// This function will create a [`SyncHttpResolver`] that returns [`Error::SyncHttpResolverNotImplemented`] + /// This function will create a [`SyncHttpResolver`] that returns [`HttpResolverError::SyncHttpResolverNotImplemented`] /// under any of the following conditions: /// * If both `http_reqwest_blocking` and `http_ureq` aren't enabled. /// * If the platform is WASM. @@ -198,7 +198,7 @@ pub struct AsyncGenericResolver { impl AsyncGenericResolver { /// Create a new [`AsyncGenericResolver`] with an auto-specified [`AsyncHttpResolver`]. /// - /// This function will create a [`AsyncHttpResolver`] that returns [`Error::AsyncHttpResolverNotImplemented`] + /// This function will create a [`AsyncHttpResolver`] that returns [`HttpResolverError::AsyncHttpResolverNotImplemented`] /// under any of the following conditions: /// * If `http_reqwest` isn't enabled. /// * If the platform is WASI and `http_wstd` isn't enabled. @@ -252,7 +252,6 @@ pub enum HttpResolverError { /// The remote URI is blocked by the allowed list. /// /// The allowed list can be set via: - /// - [`SyncGenericResolver::set_allowed_hosts`] / [`AsyncGenericResolver::set_allowed_hosts`] /// - [`RestrictedResolver`] (for wrapping custom resolvers) /// - SDK settings via [`Core::allowed_network_hosts`] /// From 82ee8d97ea2326d7fa450f2f1625989904a01119 Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 12 Feb 2026 15:47:50 -0500 Subject: [PATCH 12/14] fix: private http module for now in this PR --- sdk/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 7b992ff86..13bf31a74 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -212,7 +212,7 @@ pub mod crypto; pub mod dynamic_assertion; /// The `http` module contains generic traits for configuring sync and async HTTP resolvers. -pub mod http; +pub(crate) mod http; /// The `identity` module provides support for the [CAWG identity assertion](https://cawg.io/identity). #[doc(hidden)] From 0811740f0d4b00e9d53aa63b8fbbdad3ce1f5c67 Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 12 Feb 2026 15:52:19 -0500 Subject: [PATCH 13/14] docs: link TODO to issue --- sdk/src/identity/x509/async_x509_credential_holder.rs | 2 +- sdk/src/identity/x509/x509_credential_holder.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/identity/x509/async_x509_credential_holder.rs b/sdk/src/identity/x509/async_x509_credential_holder.rs index 762ae11c2..f5a990d79 100644 --- a/sdk/src/identity/x509/async_x509_credential_holder.rs +++ b/sdk/src/identity/x509/async_x509_credential_holder.rs @@ -92,7 +92,7 @@ impl AsyncCredentialHolder for AsyncX509CredentialHolder { &sp_cbor, None, TimeStampStorage::V2_sigTst2_CTT, - // TODO: pass in as parameter + // TODO: https://github.com/contentauth/c2pa-rs/issues/1645 &AsyncGenericResolver::new(), ) .await diff --git a/sdk/src/identity/x509/x509_credential_holder.rs b/sdk/src/identity/x509/x509_credential_holder.rs index 384de8b33..24be5464b 100644 --- a/sdk/src/identity/x509/x509_credential_holder.rs +++ b/sdk/src/identity/x509/x509_credential_holder.rs @@ -65,7 +65,7 @@ impl CredentialHolder for X509CredentialHolder { &sp_cbor, None, TimeStampStorage::V2_sigTst2_CTT, - // TODO: pass in as parameter + // TODO: https://github.com/contentauth/c2pa-rs/issues/1645 &SyncGenericResolver::new(), ) .map_err(|e| IdentityBuilderError::SignerError(e.to_string())) From 58e3aca9343ea3e9446c1ac5f1cadd939d266c02 Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 12 Feb 2026 17:05:59 -0500 Subject: [PATCH 14/14] feat!: call `Signer::send_timestamp_request` for timestamp assertions --- sdk/src/assertions/timestamp.rs | 114 ++++++++++++++++++++++++++++---- sdk/src/builder.rs | 30 +++++---- 2 files changed, 118 insertions(+), 26 deletions(-) diff --git a/sdk/src/assertions/timestamp.rs b/sdk/src/assertions/timestamp.rs index 690e84e2d..f7906ec46 100644 --- a/sdk/src/assertions/timestamp.rs +++ b/sdk/src/assertions/timestamp.rs @@ -24,7 +24,7 @@ use crate::{ error::Result, http::{AsyncHttpResolver, SyncHttpResolver}, status_tracker::StatusTracker, - Error, + AsyncSigner, Error, Signer, }; /// Helper class to create a `TimeStamp` assertion. @@ -60,9 +60,11 @@ impl TimeStamp { /// The signature is expected to be the `signature` field of the `COSE_Sign1_Tagged` structure /// found in the C2PA claim signature box of the manifest corresponding to the `manifest_id`. // - // The `signature` is normally obtained from [`Store::get_cose_sign1_signature`]. + // The `signature` is normally obtained via [`Claim::cose_sign1`] using the [`CoseSign1::signature`] + // field. // - // [`Store::get_cose_sign1_signature`][crate::store::Store::get_cose_sign1_structure]. + // [`Claim::cose_sign1`][crate::claim::Claim::cose_sign1]. + // [`CoseSign1::signature`][coset::CoseSign1::signature]. #[async_generic(async_signature( &mut self, tsa_url: &str, @@ -70,7 +72,7 @@ impl TimeStamp { signature: &[u8], http_resolver: &impl AsyncHttpResolver, ))] - pub(crate) fn refresh_timestamp( + pub fn refresh_timestamp( &mut self, tsa_url: &str, manifest_id: &str, @@ -99,7 +101,7 @@ impl TimeStamp { message: &[u8], http_resolver: &impl AsyncHttpResolver, ))] - pub(crate) fn send_timestamp_token_request( + pub fn send_timestamp_token_request( tsa_url: &str, message: &[u8], http_resolver: &impl SyncHttpResolver, @@ -127,13 +129,102 @@ impl TimeStamp { } .map_err(|err| Error::OtherError(format!("timestamp token not found: {err:?}").into()))?; - // make sure it is a good response + if _sync { + Self::verify_timestamp_response(&bytes, message)?; + } else { + Self::verify_timestamp_response_async(&bytes, message).await?; + } + + let token = + crate::crypto::cose::timestamptoken_from_timestamprsp(&bytes).map_err(|err| { + Error::OtherError(format!("timestamp token not found: {err:?}").into()) + })?; + + Ok(token) + } + + /// Equivalent to [`TimeStamp::refresh_timestamp`] except using a [`Signer`]. + /// + /// [`Signer`]: crate::Signer + #[async_generic(async_signature( + &mut self, + manifest_id: &str, + signature: &[u8], + http_resolver: &impl AsyncHttpResolver, + signer: &(impl AsyncSigner + ?Sized), + ))] + pub(crate) fn refresh_timestamp_with_signer( + &mut self, + manifest_id: &str, + signature: &[u8], + http_resolver: &impl SyncHttpResolver, + signer: &(impl Signer + ?Sized), + ) -> Result<()> { + let timestamp_token = if _sync { + TimeStamp::send_timestamp_token_request_with_signer(signature, http_resolver, signer)? + } else { + TimeStamp::send_timestamp_token_request_with_signer_async( + signature, + http_resolver, + signer, + ) + .await? + }; + + self.0 + .insert(manifest_id.to_owned(), ByteBuf::from(timestamp_token)); + + Ok(()) + } + + /// Equivalent to [`TimeStamp::send_timestamp_token_request`] except using a [`Signer`]. + /// + /// [`Signer`]: crate::Signer + #[async_generic(async_signature( + message: &[u8], + http_resolver: &impl AsyncHttpResolver, + signer: &(impl AsyncSigner + ?Sized), + ))] + pub(crate) fn send_timestamp_token_request_with_signer( + message: &[u8], + http_resolver: &impl SyncHttpResolver, + signer: &(impl Signer + ?Sized), + ) -> Result> { + let bytes = if _sync { + signer.send_timestamp_request(http_resolver, message) + } else { + signer.send_timestamp_request(http_resolver, message).await + } + // TODO: more explicit error + .ok_or_else(|| Error::UnsupportedType)? + .map_err(|err| Error::OtherError(format!("timestamp token not found: {err:?}").into()))?; + + if _sync { + Self::verify_timestamp_response(&bytes, message)?; + } else { + Self::verify_timestamp_response_async(&bytes, message).await?; + } + + let token = + crate::crypto::cose::timestamptoken_from_timestamprsp(&bytes).map_err(|err| { + Error::OtherError(format!("timestamp token not found: {err:?}").into()) + })?; + + Ok(token) + } + + /// Verifies a timestamp respponse given the response bytes and original message. + #[async_generic(async_signature( + bytes: &[u8], + message: &[u8], + ))] + fn verify_timestamp_response(bytes: &[u8], message: &[u8]) -> Result<()> { let ctp = CertificateTrustPolicy::passthrough(); let mut tracker = StatusTracker::default(); if _sync { crate::crypto::time_stamp::verify_time_stamp( - &bytes, + bytes, message, &ctp, &mut tracker, @@ -141,7 +232,7 @@ impl TimeStamp { )?; } else { crate::crypto::time_stamp::verify_time_stamp_async( - &bytes, + bytes, message, &ctp, &mut tracker, @@ -150,12 +241,7 @@ impl TimeStamp { .await?; } - let token = - crate::crypto::cose::timestamptoken_from_timestamprsp(&bytes).map_err(|err| { - Error::OtherError(format!("timestamp token not found: {err:?}").into()) - })?; - - Ok(token) + Ok(()) } } diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 1c88c8138..20e210033 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1787,10 +1787,9 @@ impl Builder { /// [`TimeStampSettings::enabled`]: crate::settings::builder::TimeStampSettings::enabled #[async_generic(async_signature( &self, - tsa_url: &str, provenance_claim: &mut Claim, ))] - fn maybe_add_timestamp(&self, tsa_url: &str, provenance_claim: &mut Claim) -> Result<()> { + fn maybe_add_timestamp(&self, provenance_claim: &mut Claim) -> Result<()> { let settings = self.context().settings(); if !settings.builder.auto_timestamp_assertion.enabled @@ -1799,6 +1798,15 @@ impl Builder { return Ok(()); } + let signer = if _sync { + self.context().signer()? + } else { + self.context().async_signer()? + }; + if signer.time_authority_url().is_none() { + return Ok(()); + } + let mut claim_uris = HashSet::new(); match settings.builder.auto_timestamp_assertion.fetch_scope { TimeStampFetchScope::All => { @@ -1868,19 +1876,19 @@ impl Builder { if let Some(claim) = provenance_claim.claim_ingredient(&manifest_label) { let signature = claim.cose_sign1()?.signature; if _sync { - timestamp_assertion.refresh_timestamp( - tsa_url, + timestamp_assertion.refresh_timestamp_with_signer( &manifest_label, &signature, &self.context().resolver(), + signer, )?; } else { timestamp_assertion - .refresh_timestamp_async( - tsa_url, + .refresh_timestamp_with_signer_async( &manifest_label, &signature, &self.context().resolver_async(), + signer, ) .await?; } @@ -2074,12 +2082,10 @@ impl Builder { let mut claim = self.to_claim()?; - if let Some(tsa_url) = signer.time_authority_url() { - if _sync { - self.maybe_add_timestamp(&tsa_url, &mut claim)?; - } else { - self.maybe_add_timestamp_async(&tsa_url, &mut claim).await? - } + if _sync { + self.maybe_add_timestamp(&mut claim)?; + } else { + self.maybe_add_timestamp_async(&mut claim).await? } let mut store = self.to_store_with_claim(claim)?;