Skip to content

Commit 1b8ed85

Browse files
feat: Add support for referenced_assertions and roles (#1032)
(Q&D implementation: Will work on better testing in a day or three.)
1 parent 8d39380 commit 1b8ed85

File tree

5 files changed

+94
-11
lines changed

5 files changed

+94
-11
lines changed

cawg_identity/src/builder/identity_assertion_builder.rs

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
// specific language governing permissions and limitations under
1212
// each license.
1313

14+
use std::collections::HashSet;
15+
1416
use async_trait::async_trait;
1517
use c2pa::dynamic_assertion::{
1618
AsyncDynamicAssertion, DynamicAssertion, DynamicAssertionContent, PartialClaim,
@@ -34,7 +36,8 @@ use crate::{builder::AsyncCredentialHolder, IdentityAssertion, SignerPayload};
3436
/// [`IdentityAssertionSigner`]: crate::builder::IdentityAssertionSigner
3537
pub struct IdentityAssertionBuilder {
3638
credential_holder: Box<dyn CredentialHolder>,
37-
// referenced_assertions: Vec<MumbleSomething>,
39+
referenced_assertions: HashSet<String>,
40+
roles: Vec<String>,
3841
}
3942

4043
impl IdentityAssertionBuilder {
@@ -43,6 +46,30 @@ impl IdentityAssertionBuilder {
4346
pub fn for_credential_holder<CH: CredentialHolder + 'static>(credential_holder: CH) -> Self {
4447
Self {
4548
credential_holder: Box::new(credential_holder),
49+
referenced_assertions: HashSet::new(),
50+
roles: vec![],
51+
}
52+
}
53+
54+
/// Add assertion labels to consider as referenced_assertions.
55+
///
56+
/// If any of these labels match assertions that are present in the partial
57+
/// claim submitted during signing, they will be added to the
58+
/// `referenced_assertions` list for this identity assertion.
59+
pub fn add_referenced_assertions(&mut self, labels: &[&str]) {
60+
for label in labels {
61+
self.referenced_assertions.insert(label.to_string());
62+
}
63+
}
64+
65+
/// Add roles to attach to the named actor for this identity assertion.
66+
///
67+
/// See [§5.1.2, “Named actor roles,”] for more information.
68+
///
69+
/// [§5.1.2, “Named actor roles,”]: https://cawg.io/identity/1.1-draft/#_named_actor_roles
70+
pub fn add_roles(&mut self, roles: &[&str]) {
71+
for role in roles {
72+
self.roles.push(role.to_string());
4673
}
4774
}
4875
}
@@ -64,20 +91,31 @@ impl DynamicAssertion for IdentityAssertionBuilder {
6491
size: Option<usize>,
6592
claim: &PartialClaim,
6693
) -> c2pa::Result<DynamicAssertionContent> {
67-
// TO DO: Better filter for referenced assertions.
68-
// For now, just require hard binding.
69-
7094
// TO DO: Update to respond correctly when identity assertions refer to each
7195
// other.
7296
let referenced_assertions = claim
7397
.assertions()
74-
.filter(|a| a.url().contains("c2pa.assertions/c2pa.hash."))
98+
.filter(|a| {
99+
// Always accept the hard binding assertion.
100+
if a.url().contains("c2pa.assertions/c2pa.hash.") {
101+
return true;
102+
}
103+
104+
let label = if let Some((_, label)) = a.url().rsplit_once('/') {
105+
label.to_string()
106+
} else {
107+
a.url()
108+
};
109+
110+
self.referenced_assertions.contains(&label)
111+
})
75112
.cloned()
76113
.collect();
77114

78115
let signer_payload = SignerPayload {
79116
referenced_assertions,
80117
sig_type: self.credential_holder.sig_type().to_owned(),
118+
roles: self.roles.clone(),
81119
};
82120

83121
let signature_result = self.credential_holder.sign(&signer_payload);
@@ -100,7 +138,9 @@ pub struct AsyncIdentityAssertionBuilder {
100138

101139
#[cfg(target_arch = "wasm32")]
102140
credential_holder: Box<dyn AsyncCredentialHolder>,
103-
// referenced_assertions: Vec<MumbleSomething>,
141+
142+
referenced_assertions: HashSet<String>,
143+
roles: Vec<String>,
104144
}
105145

106146
impl AsyncIdentityAssertionBuilder {
@@ -111,6 +151,30 @@ impl AsyncIdentityAssertionBuilder {
111151
) -> Self {
112152
Self {
113153
credential_holder: Box::new(credential_holder),
154+
referenced_assertions: HashSet::new(),
155+
roles: vec![],
156+
}
157+
}
158+
159+
/// Add assertion labels to consider as referenced_assertions.
160+
///
161+
/// If any of these labels match assertions that are present in the partial
162+
/// claim submitted during signing, they will be added to the
163+
/// `referenced_assertions` list for this identity assertion.
164+
pub fn add_referenced_assertions(&mut self, labels: &[&str]) {
165+
for label in labels {
166+
self.referenced_assertions.insert(label.to_string());
167+
}
168+
}
169+
170+
/// Add roles to attach to the named actor for this identity assertion.
171+
///
172+
/// See [§5.1.2, “Named actor roles,”] for more information.
173+
///
174+
/// [§5.1.2, “Named actor roles,”]: https://cawg.io/identity/1.1-draft/#_named_actor_roles
175+
pub fn add_roles(&mut self, roles: &[&str]) {
176+
for role in roles {
177+
self.roles.push(role.to_string());
114178
}
115179
}
116180
}
@@ -134,20 +198,31 @@ impl AsyncDynamicAssertion for AsyncIdentityAssertionBuilder {
134198
size: Option<usize>,
135199
claim: &PartialClaim,
136200
) -> c2pa::Result<DynamicAssertionContent> {
137-
// TO DO: Better filter for referenced assertions.
138-
// For now, just require hard binding.
139-
140201
// TO DO: Update to respond correctly when identity assertions refer to each
141202
// other.
142203
let referenced_assertions = claim
143204
.assertions()
144-
.filter(|a| a.url().contains("c2pa.assertions/c2pa.hash."))
205+
.filter(|a| {
206+
// Always accept the hard binding assertion.
207+
if a.url().contains("c2pa.assertions/c2pa.hash.") {
208+
return true;
209+
}
210+
211+
let label = if let Some((_, label)) = a.url().rsplit_once('/') {
212+
label.to_string()
213+
} else {
214+
a.url()
215+
};
216+
217+
self.referenced_assertions.contains(&label)
218+
})
145219
.cloned()
146220
.collect();
147221

148222
let signer_payload = SignerPayload {
149223
referenced_assertions,
150224
sig_type: self.credential_holder.sig_type().to_owned(),
225+
roles: self.roles.clone(),
151226
};
152227

153228
let signature_result = self.credential_holder.sign(&signer_payload).await;

cawg_identity/src/identity_assertion/signer_payload.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ pub struct SignerPayload {
3737

3838
/// A string identifying the data type of the `signature` field
3939
pub sig_type: String,
40-
// TO DO: Add role and expected_* fields.
40+
41+
/// Roles associated with the named actor
42+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
43+
#[serde(rename = "role")]
44+
pub roles: Vec<String>,
45+
// TO DO: Add expected_* fields.
4146
// (https://github.com/contentauth/c2pa-rs/issues/816)
4247
}
4348

cawg_identity/src/tests/claim_aggregation/interop.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ async fn adobe_connected_identities() {
8585
None,
8686
&hex_literal::hex!("58514c7072376d453164794f783477317a716e4f63716159325a594d686a5031526c7a552f7877614259383d")
8787
)],
88+
roles: vec!(),
8889
sig_type: "cawg.identity_claims_aggregation".to_owned(),
8990
}
9091
);

cawg_identity/src/tests/identity_assertion/built_in_signature_verifier.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ async fn adobe_connected_identities() {
179179
None,
180180
&hex_literal::hex!("58514c7072376d453164794f783477317a716e4f63716159325a594d686a5031526c7a552f7877614259383d")
181181
)],
182+
roles: vec!(),
182183
sig_type: "cawg.identity_claims_aggregation".to_owned(),
183184
}
184185
);

cawg_identity/src/tests/identity_assertion/signer_payload.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ fn impl_clone() {
3333

3434
let signer_payload = SignerPayload {
3535
referenced_assertions: vec![{ data_hash_ref }],
36+
roles: vec![],
3637
sig_type: "NONSENSE".to_owned(),
3738
};
3839

0 commit comments

Comments
 (0)