Skip to content

Commit c820aa9

Browse files
feat(cawg_identity): Split CredentialHolder into sync and async versions (#891)
1 parent 0b9f122 commit c820aa9

18 files changed

+725
-277
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright 2025 Adobe. All rights reserved.
2+
// This file is licensed to you under the Apache License,
3+
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
4+
// or the MIT license (http://opensource.org/licenses/MIT),
5+
// at your option.
6+
7+
// Unless required by applicable law or agreed to in writing,
8+
// this software is distributed on an "AS IS" BASIS, WITHOUT
9+
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
10+
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
11+
// specific language governing permissions and limitations under
12+
// each license.
13+
14+
use async_trait::async_trait;
15+
use c2pa::{AsyncDynamicAssertion, AsyncSigner, Result};
16+
use c2pa_crypto::raw_signature::{AsyncRawSigner, SigningAlg};
17+
18+
use crate::builder::AsyncIdentityAssertionBuilder;
19+
20+
/// An `AsyncIdentityAssertionSigner` extends the [`AsyncSigner`] interface to
21+
/// add zero or more identity assertions to a C2PA [`Manifest`] that is being
22+
/// produced.
23+
///
24+
/// [`AsyncSigner`]: c2pa::AsyncSigner
25+
/// [`Manifest`]: c2pa::Manifest
26+
pub struct AsyncIdentityAssertionSigner {
27+
#[cfg(not(target_arch = "wasm32"))]
28+
signer: Box<dyn AsyncRawSigner + Sync + Send>,
29+
30+
#[cfg(target_arch = "wasm32")]
31+
signer: Box<dyn AsyncRawSigner>,
32+
33+
#[cfg(not(target_arch = "wasm32"))]
34+
identity_assertions: std::sync::RwLock<Vec<AsyncIdentityAssertionBuilder>>,
35+
36+
#[cfg(target_arch = "wasm32")]
37+
identity_assertions: std::cell::RefCell<Vec<AsyncIdentityAssertionBuilder>>,
38+
}
39+
40+
impl AsyncIdentityAssertionSigner {
41+
/// Create an `AsyncIdentityAssertionSigner` wrapping the provided
42+
/// [`AsyncRawSigner`] instance.
43+
#[cfg(not(target_arch = "wasm32"))]
44+
pub fn new(signer: Box<dyn AsyncRawSigner + Sync + Send>) -> Self {
45+
Self {
46+
signer,
47+
identity_assertions: Self::ia_default(),
48+
}
49+
}
50+
51+
/// Create an `AsyncIdentityAssertionSigner` wrapping the provided
52+
/// [`AsyncRawSigner`] instance.
53+
#[cfg(target_arch = "wasm32")]
54+
pub fn new(signer: Box<dyn AsyncRawSigner>) -> Self {
55+
Self {
56+
signer,
57+
identity_assertions: Self::ia_default(),
58+
}
59+
}
60+
61+
/// (FOR USE BY INTERNAL TESTS ONLY): Create an AsyncIdentityAssertionSigner
62+
/// using test credentials for a particular algorithm.
63+
#[cfg(test)]
64+
pub(crate) fn from_test_credentials(alg: SigningAlg) -> Self {
65+
use c2pa_crypto::raw_signature::async_signer_from_cert_chain_and_private_key;
66+
67+
use crate::tests::fixtures::cert_chain_and_private_key_for_alg;
68+
69+
let (cert_chain, private_key) = cert_chain_and_private_key_for_alg(alg);
70+
71+
#[allow(clippy::unwrap_used)]
72+
Self {
73+
signer: async_signer_from_cert_chain_and_private_key(
74+
&cert_chain,
75+
&private_key,
76+
alg,
77+
None,
78+
)
79+
.unwrap(),
80+
identity_assertions: Self::ia_default(),
81+
}
82+
}
83+
84+
#[cfg(not(target_arch = "wasm32"))]
85+
fn ia_default() -> std::sync::RwLock<Vec<AsyncIdentityAssertionBuilder>> {
86+
std::sync::RwLock::new(vec![])
87+
}
88+
89+
#[cfg(target_arch = "wasm32")]
90+
fn ia_default() -> std::cell::RefCell<Vec<AsyncIdentityAssertionBuilder>> {
91+
std::cell::RefCell::new(vec![])
92+
}
93+
94+
/// Add an [`AsyncIdentityAssertionBuilder`] to be used when signing the
95+
/// next [`Manifest`].
96+
///
97+
/// IMPORTANT: When [`sign()`] is called, the list of
98+
/// [`AsyncIdentityAssertionBuilder`]s will be cleared.
99+
///
100+
/// [`Manifest`]: c2pa::Manifest
101+
/// [`sign()`]: Self::sign
102+
pub fn add_identity_assertion(&mut self, iab: AsyncIdentityAssertionBuilder) {
103+
#[cfg(not(target_arch = "wasm32"))]
104+
{
105+
#[allow(clippy::unwrap_used)]
106+
let mut identity_assertions = self.identity_assertions.write().unwrap();
107+
// TO DO: Replace with error handling in the very unlikely case of a panic here.
108+
identity_assertions.push(iab);
109+
}
110+
111+
#[cfg(target_arch = "wasm32")]
112+
{
113+
#[allow(clippy::unwrap_used)]
114+
let mut identity_assertions = self.identity_assertions.try_borrow_mut().unwrap();
115+
// TO DO: Replace with error handling in the very unlikely case of a panic here.
116+
identity_assertions.push(iab);
117+
}
118+
}
119+
}
120+
121+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
122+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
123+
impl AsyncSigner for AsyncIdentityAssertionSigner {
124+
async fn sign(&self, data: Vec<u8>) -> Result<Vec<u8>> {
125+
self.signer.sign(data).await.map_err(|e| e.into())
126+
}
127+
128+
fn alg(&self) -> SigningAlg {
129+
self.signer.alg()
130+
}
131+
132+
fn certs(&self) -> Result<Vec<Vec<u8>>> {
133+
self.signer.cert_chain().map_err(|e| e.into())
134+
}
135+
136+
fn reserve_size(&self) -> usize {
137+
self.signer.reserve_size()
138+
}
139+
140+
async fn ocsp_val(&self) -> Option<Vec<u8>> {
141+
self.signer.ocsp_response().await
142+
}
143+
144+
fn time_authority_url(&self) -> Option<String> {
145+
self.signer.time_stamp_service_url()
146+
}
147+
148+
fn timestamp_request_headers(&self) -> Option<Vec<(String, String)>> {
149+
self.signer.time_stamp_request_headers()
150+
}
151+
152+
fn timestamp_request_body(&self, message: &[u8]) -> Result<Vec<u8>> {
153+
self.signer
154+
.time_stamp_request_body(message)
155+
.map_err(|e| e.into())
156+
}
157+
158+
async fn send_timestamp_request(&self, message: &[u8]) -> Option<Result<Vec<u8>>> {
159+
self.signer
160+
.send_time_stamp_request(message)
161+
.await
162+
.map(|r| r.map_err(|e| e.into()))
163+
}
164+
165+
fn async_raw_signer(&self) -> Option<Box<&dyn AsyncRawSigner>> {
166+
Some(Box::new(&*self.signer))
167+
}
168+
169+
fn dynamic_assertions(&self) -> Vec<Box<dyn AsyncDynamicAssertion>> {
170+
#[cfg(not(target_arch = "wasm32"))]
171+
{
172+
#[allow(clippy::unwrap_used)]
173+
let mut identity_assertions = self.identity_assertions.write().unwrap();
174+
// TO DO: Replace with error handling in the very unlikely case of a panic here.
175+
176+
let ia_clone = identity_assertions.split_off(0);
177+
let mut dynamic_assertions: Vec<Box<dyn AsyncDynamicAssertion>> = vec![];
178+
179+
for ia in ia_clone.into_iter() {
180+
dynamic_assertions.push(Box::new(ia));
181+
}
182+
183+
dynamic_assertions
184+
}
185+
186+
#[cfg(target_arch = "wasm32")]
187+
{
188+
#[allow(clippy::unwrap_used)]
189+
let mut identity_assertions = self.identity_assertions.try_borrow_mut().unwrap();
190+
// TO DO: Replace with error handling in the very unlikely case of a panic here.
191+
192+
let ia_clone = identity_assertions.split_off(0);
193+
let mut dynamic_assertions: Vec<Box<dyn AsyncDynamicAssertion>> = vec![];
194+
195+
for ia in ia_clone.into_iter() {
196+
dynamic_assertions.push(Box::new(ia));
197+
}
198+
199+
dynamic_assertions
200+
}
201+
}
202+
}

cawg_identity/src/builder/credential_holder.rs

+42-6
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,44 @@ use async_trait::async_trait;
1515

1616
use crate::{builder::IdentityBuilderError, SignerPayload};
1717

18-
/// An implementation of `CredentialHolder` is able to generate a signature over
19-
/// the [`SignerPayload`] data structure on behalf of a credential holder.
18+
/// An implementation of `CredentialHolder` is able to generate a signature
19+
/// over the [`SignerPayload`] data structure on behalf of a credential holder.
20+
///
21+
/// If network calls are to be made, it is better to implement
22+
/// [`AsyncCredentialHolder`].
23+
///
24+
/// Implementations of this trait will specialize based on the kind of
25+
/// credential as specified in [§8. Credentials, signatures, and validation
26+
/// methods] from the CAWG Identity Assertion specification.
27+
///
28+
/// [§8. Credentials, signatures, and validation methods]: https://cawg.io/identity/1.1-draft/#_credentials_signatures_and_validation_methods
29+
pub trait CredentialHolder {
30+
/// Returns the designated `sig_type` value for this kind of credential.
31+
fn sig_type(&self) -> &'static str;
32+
33+
/// Returns the maximum expected size in bytes of the `signature`
34+
/// field for the identity assertion which will be subsequently
35+
/// returned by the [`sign`] function. Signing will fail if the
36+
/// subsequent signature is larger than this number of bytes.
37+
///
38+
/// [`sign`]: Self::sign
39+
fn reserve_size(&self) -> usize;
40+
41+
/// Signs the [`SignerPayload`] data structure on behalf of the credential
42+
/// holder.
43+
///
44+
/// If successful, returns the exact binary content to be placed in
45+
/// the `signature` field for this identity assertion.
46+
///
47+
/// The signature MUST NOT be larger than the size previously stated
48+
/// by the [`reserve_size`] function.
49+
///
50+
/// [`reserve_size`]: Self::reserve_size
51+
fn sign(&self, signer_payload: &SignerPayload) -> Result<Vec<u8>, IdentityBuilderError>;
52+
}
53+
54+
/// An implementation of `AsyncCredentialHolder` is able to generate a signature
55+
/// over the [`SignerPayload`] data structure on behalf of a credential holder.
2056
///
2157
/// Implementations of this trait will specialize based on the kind of
2258
/// credential as specified in [§8. Credentials, signatures, and validation
@@ -25,7 +61,7 @@ use crate::{builder::IdentityBuilderError, SignerPayload};
2561
/// [§8. Credentials, signatures, and validation methods]: https://cawg.io/identity/1.1-draft/#_credentials_signatures_and_validation_methods
2662
#[cfg(not(target_arch = "wasm32"))]
2763
#[async_trait]
28-
pub trait CredentialHolder: Send + Sync {
64+
pub trait AsyncCredentialHolder: Send + Sync {
2965
/// Returns the designated `sig_type` value for this kind of credential.
3066
fn sig_type(&self) -> &'static str;
3167

@@ -50,8 +86,8 @@ pub trait CredentialHolder: Send + Sync {
5086
async fn sign(&self, signer_payload: &SignerPayload) -> Result<Vec<u8>, IdentityBuilderError>;
5187
}
5288

53-
/// An implementation of `CredentialHolder` is able to generate a signature over
54-
/// the [`SignerPayload`] data structure on behalf of a credential holder.
89+
/// An implementation of `AsyncCredentialHolder` is able to generate a signature
90+
/// over the [`SignerPayload`] data structure on behalf of a credential holder.
5591
///
5692
/// Implementations of this trait will specialize based on the kind of
5793
/// credential as specified in [§8. Credentials, signatures, and validation
@@ -60,7 +96,7 @@ pub trait CredentialHolder: Send + Sync {
6096
/// [§8. Credentials, signatures, and validation methods]: https://cawg.io/identity/1.1-draft/#_credentials_signatures_and_validation_methods
6197
#[cfg(target_arch = "wasm32")]
6298
#[async_trait(?Send)]
63-
pub trait CredentialHolder {
99+
pub trait AsyncCredentialHolder {
64100
/// Returns the designated `sig_type` value for this kind of credential.
65101
fn sig_type(&self) -> &'static str;
66102

0 commit comments

Comments
 (0)