Skip to content

Commit fd8ba15

Browse files
feat: Add StatusTracker to IdentityAssertion parsing and validation APIs (#943)
1 parent 0b1c019 commit fd8ba15

File tree

24 files changed

+1606
-45
lines changed

24 files changed

+1606
-45
lines changed

cawg_identity/src/claim_aggregation/ica_signature_verifier.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// each license.
1313

1414
use async_trait::async_trait;
15+
use c2pa_status_tracker::{log_item, StatusTracker};
1516
use coset::{CoseSign1, RegisteredLabelWithPrivate, TaggedCborSerializable};
1617

1718
use crate::{
@@ -45,13 +46,34 @@ impl SignatureVerifier for IcaSignatureVerifier {
4546

4647
async fn check_signature(
4748
&self,
48-
_signer_payload: &SignerPayload,
49+
signer_payload: &SignerPayload,
4950
signature: &[u8],
51+
status_tracker: &mut StatusTracker,
5052
) -> Result<Self::Output, ValidationError<Self::Error>> {
53+
if signer_payload.sig_type != super::CAWG_ICA_SIG_TYPE {
54+
// TO DO: Where would we get assertion label?
55+
log_item!(
56+
"NEED TO FIND LABEL".to_owned(),
57+
"unsupported signature type",
58+
"X509SignatureVerifier::check_signature"
59+
)
60+
.validation_status("cawg.identity.sig_type.unknown")
61+
.failure_no_throw(
62+
status_tracker,
63+
ValidationError::<IcaValidationError>::UnknownSignatureType(
64+
signer_payload.sig_type.clone(),
65+
),
66+
);
67+
68+
return Err(ValidationError::UnknownSignatureType(
69+
signer_payload.sig_type.clone(),
70+
));
71+
}
72+
5173
// The signature should be a `CoseSign1` object.
5274
let sign1 = CoseSign1::from_tagged_slice(signature)?;
5375

54-
// Identify the signature
76+
// Identify the signature.
5577
let _ssi_alg = if let Some(ref alg) = sign1.protected.header.alg {
5678
match alg {
5779
// TEMPORARY: Require EdDSA algorithm.

cawg_identity/src/claim_aggregation/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ mod ica_validation_error;
2929
pub use ica_validation_error::IcaValidationError;
3030

3131
pub(crate) mod w3c_vc;
32+
33+
const CAWG_ICA_SIG_TYPE: &str = "cawg.identity_claims_aggregation";

cawg_identity/src/identity_assertion/assertion.rs

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use std::{
1717
};
1818

1919
use c2pa::{Manifest, Reader};
20+
use c2pa_status_tracker::{log_item, StatusTracker};
2021
use serde::{Deserialize, Serialize};
2122
use serde_bytes::ByteBuf;
2223

@@ -63,14 +64,31 @@ impl IdentityAssertion {
6364
/// Iterator returns a [`Result`] because each assertion may fail to parse.
6465
///
6566
/// Aside from CBOR parsing, no further validation is performed.
66-
pub fn from_manifest(
67-
manifest: &Manifest,
68-
) -> impl Iterator<Item = Result<Self, c2pa::Error>> + use<'_> {
67+
pub fn from_manifest<'a>(
68+
manifest: &'a Manifest,
69+
status_tracker: &'a mut StatusTracker,
70+
) -> impl Iterator<Item = Result<Self, c2pa::Error>> + use<'a> {
6971
manifest
7072
.assertions()
7173
.iter()
7274
.filter(|a| a.label().starts_with("cawg.identity"))
73-
.map(|a| a.to_assertion())
75+
.map(|a| (a.label().to_owned(), a.to_assertion()))
76+
.inspect(|(label, r)| {
77+
if let Err(err) = r {
78+
// TO DO: a.label() is probably wrong (not a full JUMBF URI)
79+
log_item!(
80+
label.clone(),
81+
"invalid CBOR",
82+
"IdentityAssertion::from_manifest"
83+
)
84+
.validation_status("cawg.identity.cbor.invalid")
85+
.failure_no_throw(
86+
status_tracker,
87+
c2pa::Error::AssertionSpecificError(err.to_string()),
88+
);
89+
}
90+
})
91+
.map(move |(_label, r)| r)
7492
}
7593

7694
/// Create a summary report from this `IdentityAssertion`.
@@ -83,25 +101,28 @@ impl IdentityAssertion {
83101
pub async fn to_summary<SV: SignatureVerifier>(
84102
&self,
85103
manifest: &Manifest,
104+
status_tracker: &mut StatusTracker,
86105
verifier: &SV,
87106
) -> impl Serialize
88107
where
89108
<SV as SignatureVerifier>::Output: 'static,
90109
{
91-
self.to_summary_impl(manifest, verifier).await
110+
self.to_summary_impl(manifest, status_tracker, verifier)
111+
.await
92112
}
93113

94114
pub(crate) async fn to_summary_impl<SV: SignatureVerifier>(
95115
&self,
96116
manifest: &Manifest,
117+
status_tracker: &mut StatusTracker,
97118
verifier: &SV,
98119
) -> IdentityAssertionReport<
99120
<<SV as SignatureVerifier>::Output as ToCredentialSummary>::CredentialSummary,
100121
>
101122
where
102123
<SV as SignatureVerifier>::Output: 'static,
103124
{
104-
match self.validate(manifest, verifier).await {
125+
match self.validate(manifest, status_tracker, verifier).await {
105126
Ok(named_actor) => {
106127
let summary = named_actor.to_summary();
107128

@@ -120,13 +141,15 @@ impl IdentityAssertion {
120141
/// Summarize all of the identity assertions found for a [`Manifest`].
121142
pub async fn summarize_all<SV: SignatureVerifier>(
122143
manifest: &Manifest,
144+
status_tracker: &mut StatusTracker,
123145
verifier: &SV,
124146
) -> impl Serialize {
125-
Self::summarize_all_impl(manifest, verifier).await
147+
Self::summarize_all_impl(manifest, status_tracker, verifier).await
126148
}
127149

128150
pub(crate) async fn summarize_all_impl<SV: SignatureVerifier>(
129151
manifest: &Manifest,
152+
status_tracker: &mut StatusTracker,
130153
verifier: &SV,
131154
) -> IdentityAssertionsForManifest<
132155
<<SV as SignatureVerifier>::Output as ToCredentialSummary>::CredentialSummary,
@@ -139,9 +162,16 @@ impl IdentityAssertion {
139162
>,
140163
> = vec![];
141164

142-
for assertion in Self::from_manifest(manifest) {
165+
let assertion_results: Vec<Result<IdentityAssertion, c2pa::Error>> =
166+
Self::from_manifest(manifest, status_tracker).collect();
167+
168+
for assertion in assertion_results {
143169
let report = match assertion {
144-
Ok(assertion) => assertion.to_summary_impl(manifest, verifier).await,
170+
Ok(assertion) => {
171+
assertion
172+
.to_summary_impl(manifest, status_tracker, verifier)
173+
.await
174+
}
145175
Err(_) => {
146176
todo!("Handle assertion failed to parse case");
147177
}
@@ -163,6 +193,7 @@ impl IdentityAssertion {
163193
#[cfg(feature = "v1_api")]
164194
pub async fn summarize_manifest_store<SV: SignatureVerifier>(
165195
store: &c2pa::ManifestStore,
196+
status_tracker: &mut StatusTracker,
166197
verifier: &SV,
167198
) -> impl Serialize {
168199
// NOTE: We can't write this using .map(...).collect() because there are async
@@ -175,7 +206,7 @@ impl IdentityAssertion {
175206
> = BTreeMap::new();
176207

177208
for (id, manifest) in store.manifests() {
178-
let report = Self::summarize_all_impl(manifest, verifier).await;
209+
let report = Self::summarize_all_impl(manifest, status_tracker, verifier).await;
179210
reports.insert(id.clone(), report);
180211
}
181212

@@ -189,6 +220,7 @@ impl IdentityAssertion {
189220
/// Summarize all of the identity assertions found for a [`Reader`].
190221
pub async fn summarize_from_reader<SV: SignatureVerifier>(
191222
reader: &Reader,
223+
status_tracker: &mut StatusTracker,
192224
verifier: &SV,
193225
) -> impl Serialize {
194226
// NOTE: We can't write this using .map(...).collect() because there are async
@@ -201,7 +233,7 @@ impl IdentityAssertion {
201233
> = BTreeMap::new();
202234

203235
for (id, manifest) in reader.manifests() {
204-
let report = Self::summarize_all_impl(manifest, verifier).await;
236+
let report = Self::summarize_all_impl(manifest, status_tracker, verifier).await;
205237
reports.insert(id.clone(), report);
206238
}
207239

@@ -222,25 +254,55 @@ impl IdentityAssertion {
222254
pub async fn validate<SV: SignatureVerifier>(
223255
&self,
224256
manifest: &Manifest,
257+
status_tracker: &mut StatusTracker,
225258
verifier: &SV,
226259
) -> Result<SV::Output, ValidationError<SV::Error>> {
227-
self.check_padding()?;
260+
// TO DO: Create new status tracker here and pass it through
261+
// the rest of this code. Then we can rewrite the log with
262+
// assertion label at the end of this process.
263+
264+
// UPDATED TO DO: Hold off until Gavin lands the post-validate branch.
265+
// Then we'll get the assertion label handed to us nicely.
266+
self.check_padding(status_tracker)?;
228267

229-
self.signer_payload.check_against_manifest(manifest)?;
268+
self.signer_payload
269+
.check_against_manifest(manifest, status_tracker)?;
230270

231271
verifier
232-
.check_signature(&self.signer_payload, &self.signature)
272+
.check_signature(&self.signer_payload, &self.signature, status_tracker)
233273
.await
234274
}
235275

236-
fn check_padding<E>(&self) -> Result<(), ValidationError<E>> {
276+
fn check_padding<E: Debug>(
277+
&self,
278+
status_tracker: &mut StatusTracker,
279+
) -> Result<(), ValidationError<E>> {
237280
if !self.pad1.iter().all(|b| *b == 0) {
238-
return Err(ValidationError::InvalidPadding);
281+
// TO DO: Where would we get assertion label?
282+
log_item!(
283+
"NEED TO FIND LABEL".to_owned(),
284+
"invalid value in pad fields",
285+
"SignerPayload::check_padding"
286+
)
287+
.validation_status("cawg.identity.pad.invalid")
288+
.failure(status_tracker, ValidationError::<E>::InvalidPadding)?;
289+
290+
// We'll only get to this line if `pad1` is invalid and the status tracker is
291+
// configured to continue through recoverable errors. In that case, we want to
292+
// avoid logging a second "invalid padding" warning if `pad2` is also invalid.
293+
return Ok(());
239294
}
240295

241296
if let Some(pad2) = self.pad2.as_ref() {
242297
if !pad2.iter().all(|b| *b == 0) {
243-
return Err(ValidationError::InvalidPadding);
298+
// TO DO: Where would we get assertion label?
299+
log_item!(
300+
"NEED TO FIND LABEL".to_owned(),
301+
"invalid value in pad fields",
302+
"SignerPayload::check_padding"
303+
)
304+
.validation_status("cawg.identity.pad.invalid")
305+
.failure(status_tracker, ValidationError::<E>::InvalidPadding)?;
244306
}
245307
}
246308

cawg_identity/src/identity_assertion/signature_verifier.rs

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

14+
use std::fmt::Debug;
15+
1416
use async_trait::async_trait;
17+
use c2pa_status_tracker::StatusTracker;
1518
use serde::Serialize;
1619

1720
use crate::{SignerPayload, ValidationError};
@@ -36,7 +39,7 @@ pub trait SignatureVerifier: Sync {
3639
/// included in the `SignatureError` variant of [`ValidationError`].
3740
///
3841
/// [`ValidationError`]: crate::ValidationError
39-
type Error;
42+
type Error: Debug;
4043

4144
/// Verify the signature, returning an instance of [`Output`] if the
4245
/// signature is valid.
@@ -46,6 +49,7 @@ pub trait SignatureVerifier: Sync {
4649
&self,
4750
signer_payload: &SignerPayload,
4851
signature: &[u8],
52+
status_tracker: &mut StatusTracker,
4953
) -> Result<Self::Output, ValidationError<Self::Error>>;
5054
}
5155

@@ -69,7 +73,7 @@ pub trait SignatureVerifier {
6973
/// included in the `SignatureError` variant of [`ValidationError`].
7074
///
7175
/// [`ValidationError`]: crate::ValidationError
72-
type Error;
76+
type Error: Debug;
7377

7478
/// Verify the signature, returning an instance of [`Output`] if the
7579
/// signature is valid.
@@ -79,6 +83,7 @@ pub trait SignatureVerifier {
7983
&self,
8084
signer_payload: &SignerPayload,
8185
signature: &[u8],
86+
status_tracker: &mut StatusTracker,
8287
) -> Result<Self::Output, ValidationError<Self::Error>>;
8388
}
8489

cawg_identity/src/identity_assertion/signer_payload.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use std::{collections::HashSet, fmt::Debug, sync::LazyLock};
1515

1616
use c2pa::{HashedUri, Manifest};
17+
use c2pa_status_tracker::{log_item, StatusTracker};
1718
use regex::Regex;
1819
use serde::{Deserialize, Serialize};
1920

@@ -41,9 +42,10 @@ pub struct SignerPayload {
4142
}
4243

4344
impl SignerPayload {
44-
pub(super) fn check_against_manifest<E>(
45+
pub(super) fn check_against_manifest<E: Debug>(
4546
&self,
4647
manifest: &Manifest,
48+
status_tracker: &mut StatusTracker,
4749
) -> Result<(), ValidationError<E>> {
4850
// All assertions mentioned in referenced_assertions also need to be referenced
4951
// in the claim.
@@ -81,9 +83,17 @@ impl SignerPayload {
8183
// ));
8284
// }
8385
} else {
84-
return Err(ValidationError::AssertionNotInClaim(
85-
ref_assertion.url().to_owned(),
86-
));
86+
// TO DO: Where would we get assertion label?
87+
log_item!(
88+
"NEED TO FIND LABEL".to_owned(),
89+
"referenced assertion not in claim",
90+
"SignerPayload::check_against_manifest"
91+
)
92+
.validation_status("cawg.identity.assertion.mismatch")
93+
.failure(
94+
status_tracker,
95+
ValidationError::<E>::AssertionNotInClaim(ref_assertion.url().to_owned()),
96+
)?;
8797
}
8898
}
8999

@@ -101,7 +111,14 @@ impl SignerPayload {
101111
false
102112
}
103113
}) {
104-
return Err(ValidationError::NoHardBindingAssertion);
114+
// TO DO: Where would we get assertion label?
115+
log_item!(
116+
"NEED TO FIND LABEL".to_owned(),
117+
"no hard binding assertion",
118+
"SignerPayload::check_against_manifest"
119+
)
120+
.validation_status("cawg.identity.hard_binding_missing")
121+
.failure(status_tracker, ValidationError::<E>::NoHardBindingAssertion)?;
105122
}
106123

107124
// Make sure no assertion references are duplicated.
@@ -110,8 +127,19 @@ impl SignerPayload {
110127
for label in &ref_assertion_labels {
111128
let label = label.clone();
112129
if labels.contains(&label) {
113-
return Err(ValidationError::DuplicateAssertionReference(label));
130+
// TO DO: Where would we get assertion label?
131+
log_item!(
132+
"NEED TO FIND LABEL".to_owned(),
133+
"multiple references to same assertion",
134+
"SignerPayload::check_against_manifest"
135+
)
136+
.validation_status("cawg.identity.assertion.duplicate")
137+
.failure(
138+
status_tracker,
139+
ValidationError::<E>::DuplicateAssertionReference(label.clone()),
140+
)?;
114141
}
142+
115143
labels.insert(label);
116144
}
117145

0 commit comments

Comments
 (0)