Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DO NOT MERGE: Hack the CAWG SDK to create error test case files #950

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6a4a7cc
Add StatusTracker to the IdentityAssertion entry points
scouten-adobe Feb 25, 2025
ef95c5f
Signal invalid CBOR through new status tracking path
scouten-adobe Feb 27, 2025
674bbd6
Merge branch 'main' into cawg-validate+status-tracker
scouten-adobe Feb 27, 2025
1592e18
Add links from tests to spec
scouten-adobe Feb 27, 2025
ced18a0
cargo fmt
scouten-adobe Feb 27, 2025
3e4164c
Move fixtures into validation_method folder for consistency
scouten-adobe Feb 27, 2025
aa51d6f
Use doc comments to cite the section of spec we're testing
scouten-adobe Feb 27, 2025
481223c
Ensure we don't balk at extra fields in identity assertion CBOR
scouten-adobe Feb 27, 2025
14f21aa
WIP / STASH test framework for extra assertion
scouten-adobe Feb 27, 2025
da65cc2
Add test case for referenced_assertion not found in C2PA claim V1's a…
scouten-adobe Feb 27, 2025
4a2cb04
Merge branch 'main' into cawg-validate+status-tracker
scouten-adobe Feb 27, 2025
1378f7b
Merge branch 'main' into cawg-validate+status-tracker
scouten-adobe Feb 28, 2025
9b8baf8
Code for duplicate assertion case
scouten-adobe Feb 28, 2025
63cacdb
Placeholder for test case for referenced_assertion not found in C2PA …
scouten-adobe Feb 28, 2025
e9538e8
No hard binding
scouten-adobe Feb 28, 2025
433a41d
Properly handle duplicate assertion reference
scouten-adobe Feb 28, 2025
5dcfaa5
Properly handle missing reference to hard binding assertion
scouten-adobe Feb 28, 2025
ebc65ad
Invalid sig_type
scouten-adobe Feb 28, 2025
cc137a6
Back out hack for no hard binding assertion
scouten-adobe Feb 28, 2025
64a8afa
Invalid sig type asset
scouten-adobe Feb 28, 2025
6cdc7c9
Properly handle unknown signature type
scouten-adobe Mar 1, 2025
a6565b7
Merge branch 'main' into hack-cawg-sdk-for-error-test-cases
scouten-adobe Mar 1, 2025
5448d55
Merge branch 'cawg-validate+status-tracker' into hack-cawg-sdk-for-er…
scouten-adobe Mar 1, 2025
a26e200
Reverse out a couple of cases
scouten-adobe Mar 1, 2025
4019779
Place an invalid value in pad1
scouten-adobe Mar 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cawg_identity/src/builder/identity_assertion_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ fn finalize_identity_assertion(
}

ia.pad1 = vec![0u8; assertion_size - assertion_cbor.len() - 15];
ia.pad1[0] = 1; // INVALID

assertion_cbor.clear();
ciborium::into_writer(&ia, &mut assertion_cbor)
Expand Down
26 changes: 24 additions & 2 deletions cawg_identity/src/claim_aggregation/ica_signature_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// each license.

use async_trait::async_trait;
use c2pa_status_tracker::{log_item, StatusTracker};
use coset::{CoseSign1, RegisteredLabelWithPrivate, TaggedCborSerializable};

use crate::{
Expand Down Expand Up @@ -45,13 +46,34 @@ impl SignatureVerifier for IcaSignatureVerifier {

async fn check_signature(
&self,
_signer_payload: &SignerPayload,
signer_payload: &SignerPayload,
signature: &[u8],
status_tracker: &mut StatusTracker,
) -> Result<Self::Output, ValidationError<Self::Error>> {
if signer_payload.sig_type != super::CAWG_ICA_SIG_TYPE {
// TO DO: Where would we get assertion label?
log_item!(
"NEED TO FIND LABEL".to_owned(),
"unsupported signature type",
"X509SignatureVerifier::check_signature"
)
.validation_status("cawg.identity.sig_type.unknown")
.failure_no_throw(
status_tracker,
ValidationError::<IcaValidationError>::UnknownSignatureType(
signer_payload.sig_type.clone(),
),
);

return Err(ValidationError::UnknownSignatureType(
signer_payload.sig_type.clone(),
));
}

// The signature should be a `CoseSign1` object.
let sign1 = CoseSign1::from_tagged_slice(signature)?;

// Identify the signature
// Identify the signature.
let _ssi_alg = if let Some(ref alg) = sign1.protected.header.alg {
match alg {
// TEMPORARY: Require EdDSA algorithm.
Expand Down
2 changes: 2 additions & 0 deletions cawg_identity/src/claim_aggregation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ mod ica_validation_error;
pub use ica_validation_error::IcaValidationError;

pub(crate) mod w3c_vc;

const CAWG_ICA_SIG_TYPE: &str = "cawg.identity_claims_aggregation";
60 changes: 47 additions & 13 deletions cawg_identity/src/identity_assertion/assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::{
};

use c2pa::{Manifest, Reader};
use c2pa_status_tracker::{log_item, StatusTracker};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;

Expand Down Expand Up @@ -63,14 +64,31 @@ impl IdentityAssertion {
/// Iterator returns a [`Result`] because each assertion may fail to parse.
///
/// Aside from CBOR parsing, no further validation is performed.
pub fn from_manifest(
manifest: &Manifest,
) -> impl Iterator<Item = Result<Self, c2pa::Error>> + use<'_> {
pub fn from_manifest<'a>(
manifest: &'a Manifest,
status_tracker: &'a mut StatusTracker,
) -> impl Iterator<Item = Result<Self, c2pa::Error>> + use<'a> {
manifest
.assertions()
.iter()
.filter(|a| a.label().starts_with("cawg.identity"))
.map(|a| a.to_assertion())
.map(|a| (a.label().to_owned(), a.to_assertion()))
.inspect(|(label, r)| {
if let Err(err) = r {
// TO DO: a.label() is probably wrong (not a full JUMBF URI)
log_item!(
label.clone(),
"invalid CBOR",
"IdentityAssertion::from_manifest"
)
.validation_status("cawg.identity.cbor.invalid")
.failure_no_throw(
status_tracker,
c2pa::Error::AssertionSpecificError(err.to_string()),
);
}
})
.map(move |(_label, r)| r)
}

/// Create a summary report from this `IdentityAssertion`.
Expand All @@ -83,25 +101,28 @@ impl IdentityAssertion {
pub async fn to_summary<SV: SignatureVerifier>(
&self,
manifest: &Manifest,
status_tracker: &mut StatusTracker,
verifier: &SV,
) -> impl Serialize
where
<SV as SignatureVerifier>::Output: 'static,
{
self.to_summary_impl(manifest, verifier).await
self.to_summary_impl(manifest, status_tracker, verifier)
.await
}

pub(crate) async fn to_summary_impl<SV: SignatureVerifier>(
&self,
manifest: &Manifest,
status_tracker: &mut StatusTracker,
verifier: &SV,
) -> IdentityAssertionReport<
<<SV as SignatureVerifier>::Output as ToCredentialSummary>::CredentialSummary,
>
where
<SV as SignatureVerifier>::Output: 'static,
{
match self.validate(manifest, verifier).await {
match self.validate(manifest, status_tracker, verifier).await {
Ok(named_actor) => {
let summary = named_actor.to_summary();

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

pub(crate) async fn summarize_all_impl<SV: SignatureVerifier>(
manifest: &Manifest,
status_tracker: &mut StatusTracker,
verifier: &SV,
) -> IdentityAssertionsForManifest<
<<SV as SignatureVerifier>::Output as ToCredentialSummary>::CredentialSummary,
Expand All @@ -139,9 +162,16 @@ impl IdentityAssertion {
>,
> = vec![];

for assertion in Self::from_manifest(manifest) {
let assertion_results: Vec<Result<IdentityAssertion, c2pa::Error>> =
Self::from_manifest(manifest, status_tracker).collect();

for assertion in assertion_results {
let report = match assertion {
Ok(assertion) => assertion.to_summary_impl(manifest, verifier).await,
Ok(assertion) => {
assertion
.to_summary_impl(manifest, status_tracker, verifier)
.await
}
Err(_) => {
todo!("Handle assertion failed to parse case");
}
Expand All @@ -163,6 +193,7 @@ impl IdentityAssertion {
#[cfg(feature = "v1_api")]
pub async fn summarize_manifest_store<SV: SignatureVerifier>(
store: &c2pa::ManifestStore,
status_tracker: &mut StatusTracker,
verifier: &SV,
) -> impl Serialize {
// NOTE: We can't write this using .map(...).collect() because there are async
Expand All @@ -175,7 +206,7 @@ impl IdentityAssertion {
> = BTreeMap::new();

for (id, manifest) in store.manifests() {
let report = Self::summarize_all_impl(manifest, verifier).await;
let report = Self::summarize_all_impl(manifest, status_tracker, verifier).await;
reports.insert(id.clone(), report);
}

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

for (id, manifest) in reader.manifests() {
let report = Self::summarize_all_impl(manifest, verifier).await;
let report = Self::summarize_all_impl(manifest, status_tracker, verifier).await;
reports.insert(id.clone(), report);
}

Expand All @@ -222,14 +254,16 @@ impl IdentityAssertion {
pub async fn validate<SV: SignatureVerifier>(
&self,
manifest: &Manifest,
status_tracker: &mut StatusTracker,
verifier: &SV,
) -> Result<SV::Output, ValidationError<SV::Error>> {
self.check_padding()?;

self.signer_payload.check_against_manifest(manifest)?;
self.signer_payload
.check_against_manifest(manifest, status_tracker)?;

verifier
.check_signature(&self.signer_payload, &self.signature)
.check_signature(&self.signer_payload, &self.signature, status_tracker)
.await
}

Expand Down
5 changes: 4 additions & 1 deletion cawg_identity/src/identity_assertion/signature_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// each license.

use async_trait::async_trait;
use c2pa_status_tracker::StatusTracker;
use serde::Serialize;

use crate::{SignerPayload, ValidationError};
Expand All @@ -36,7 +37,7 @@ pub trait SignatureVerifier: Sync {
/// included in the `SignatureError` variant of [`ValidationError`].
///
/// [`ValidationError`]: crate::ValidationError
type Error;
type Error: std::fmt::Debug;

/// Verify the signature, returning an instance of [`Output`] if the
/// signature is valid.
Expand All @@ -46,6 +47,7 @@ pub trait SignatureVerifier: Sync {
&self,
signer_payload: &SignerPayload,
signature: &[u8],
status_tracker: &mut StatusTracker,
) -> Result<Self::Output, ValidationError<Self::Error>>;
}

Expand Down Expand Up @@ -79,6 +81,7 @@ pub trait SignatureVerifier {
&self,
signer_payload: &SignerPayload,
signature: &[u8],
status_tracker: &mut StatusTracker,
) -> Result<Self::Output, ValidationError<Self::Error>>;
}

Expand Down
40 changes: 34 additions & 6 deletions cawg_identity/src/identity_assertion/signer_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use std::{collections::HashSet, fmt::Debug, sync::LazyLock};

use c2pa::{HashedUri, Manifest};
use c2pa_status_tracker::{log_item, StatusTracker};
use regex::Regex;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -41,9 +42,10 @@ pub struct SignerPayload {
}

impl SignerPayload {
pub(super) fn check_against_manifest<E>(
pub(super) fn check_against_manifest<E: Debug>(
&self,
manifest: &Manifest,
status_tracker: &mut StatusTracker,
) -> Result<(), ValidationError<E>> {
// All assertions mentioned in referenced_assertions also need to be referenced
// in the claim.
Expand Down Expand Up @@ -81,9 +83,17 @@ impl SignerPayload {
// ));
// }
} else {
return Err(ValidationError::AssertionNotInClaim(
ref_assertion.url().to_owned(),
));
// TO DO: Where would we get assertion label?
log_item!(
"NEED TO FIND LABEL".to_owned(),
"referenced assertion not in claim",
"SignerPayload::check_against_manifest"
)
.validation_status("cawg.identity.assertion.mismatch")
.failure(
status_tracker,
ValidationError::<E>::AssertionNotInClaim(ref_assertion.url().to_owned()),
)?;
}
}

Expand All @@ -101,7 +111,14 @@ impl SignerPayload {
false
}
}) {
return Err(ValidationError::NoHardBindingAssertion);
// TO DO: Where would we get assertion label?
log_item!(
"NEED TO FIND LABEL".to_owned(),
"no hard binding assertion",
"SignerPayload::check_against_manifest"
)
.validation_status("cawg.identity.hard_binding_missing")
.failure(status_tracker, ValidationError::<E>::NoHardBindingAssertion)?;
}

// Make sure no assertion references are duplicated.
Expand All @@ -110,8 +127,19 @@ impl SignerPayload {
for label in &ref_assertion_labels {
let label = label.clone();
if labels.contains(&label) {
return Err(ValidationError::DuplicateAssertionReference(label));
// TO DO: Where would we get assertion label?
log_item!(
"NEED TO FIND LABEL".to_owned(),
"multiple references to same assertion",
"SignerPayload::check_against_manifest"
)
.validation_status("cawg.identity.assertion.duplicate")
.failure(
status_tracker,
ValidationError::<E>::DuplicateAssertionReference(label.clone()),
)?;
}

labels.insert(label);
}

Expand Down
17 changes: 9 additions & 8 deletions cawg_identity/src/tests/builder/simple_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use std::io::{Cursor, Seek};

use c2pa::{Builder, Reader, SigningAlg};
use c2pa_status_tracker::StatusTracker;
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use wasm_bindgen_test::wasm_bindgen_test;

Expand Down Expand Up @@ -72,17 +73,17 @@ async fn simple_case() {
assert_eq!(manifest_store.validation_status(), None);

let manifest = manifest_store.active_manifest().unwrap();
let mut ia_iter = IdentityAssertion::from_manifest(manifest);
let mut st = StatusTracker::default();
let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st);

// Should find exactly one identity assertion.
let ia = ia_iter.next().unwrap().unwrap();
dbg!(&ia);

assert!(ia_iter.next().is_none());
drop(ia_iter);

// And that identity assertion should be valid for this manifest.
let nsv = NaiveSignatureVerifier {};
let naive_credential = ia.validate(manifest, &nsv).await.unwrap();
let naive_credential = ia.validate(manifest, &mut st, &nsv).await.unwrap();

let nc_summary = naive_credential.to_summary();
let nc_json = serde_json::to_string(&nc_summary).unwrap();
Expand Down Expand Up @@ -127,17 +128,17 @@ async fn simple_case_async() {
assert_eq!(manifest_store.validation_status(), None);

let manifest = manifest_store.active_manifest().unwrap();
let mut ia_iter = IdentityAssertion::from_manifest(manifest);
let mut st = StatusTracker::default();
let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st);

// Should find exactly one identity assertion.
let ia = ia_iter.next().unwrap().unwrap();
dbg!(&ia);

assert!(ia_iter.next().is_none());
drop(ia_iter);

// And that identity assertion should be valid for this manifest.
let nsv = NaiveSignatureVerifier {};
let naive_credential = ia.validate(manifest, &nsv).await.unwrap();
let naive_credential = ia.validate(manifest, &mut st, &nsv).await.unwrap();

let nc_summary = naive_credential.to_summary();
let nc_json = serde_json::to_string(&nc_summary).unwrap();
Expand Down
Loading
Loading