diff --git a/cawg_identity/src/identity_assertion/assertion.rs b/cawg_identity/src/identity_assertion/assertion.rs index bb7e3ac8b..d754ba1be 100644 --- a/cawg_identity/src/identity_assertion/assertion.rs +++ b/cawg_identity/src/identity_assertion/assertion.rs @@ -17,7 +17,7 @@ use std::{ }; use c2pa::{Manifest, Reader}; -use c2pa_status_tracker::StatusTracker; +use c2pa_status_tracker::{log_item, StatusTracker}; use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; @@ -66,13 +66,29 @@ impl IdentityAssertion { /// Aside from CBOR parsing, no further validation is performed. pub fn from_manifest<'a>( manifest: &'a Manifest, - _status_tracker: &'a mut StatusTracker, + status_tracker: &'a mut StatusTracker, ) -> impl Iterator> + 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`. diff --git a/cawg_identity/src/tests/builder/simple_case.rs b/cawg_identity/src/tests/builder/simple_case.rs index 52bf526ce..f7e4f205b 100644 --- a/cawg_identity/src/tests/builder/simple_case.rs +++ b/cawg_identity/src/tests/builder/simple_case.rs @@ -15,7 +15,6 @@ use std::io::{Cursor, Seek}; use c2pa::{Builder, Reader, SigningAlg}; use c2pa_status_tracker::StatusTracker; -use serde_json::json; #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use wasm_bindgen_test::wasm_bindgen_test; @@ -24,7 +23,10 @@ use crate::{ AsyncIdentityAssertionBuilder, AsyncIdentityAssertionSigner, IdentityAssertionBuilder, IdentityAssertionSigner, }, - tests::fixtures::{NaiveAsyncCredentialHolder, NaiveCredentialHolder, NaiveSignatureVerifier}, + tests::fixtures::{ + manifest_json, parent_json, NaiveAsyncCredentialHolder, NaiveCredentialHolder, + NaiveSignatureVerifier, + }, IdentityAssertion, ToCredentialSummary, }; @@ -142,53 +144,3 @@ async fn simple_case_async() { let nc_json = serde_json::to_string(&nc_summary).unwrap(); assert_eq!(nc_json, "{}"); } - -fn manifest_json() -> String { - json!({ - "vendor": "test", - "claim_generator_info": [ - { - "name": "c2pa_test", - "version": "1.0.0" - } - ], - "metadata": [ - { - "dateTime": "1985-04-12T23:20:50.52Z", - "my_custom_metadata": "my custom metatdata value" - } - ], - "title": "Test_Manifest", - "format": "image/tiff", - "instance_id": "1234", - "thumbnail": { - "format": "image/jpeg", - "identifier": "thumbnail.jpg" - }, - "ingredients": [ - { - "title": "Test", - "format": "image/jpeg", - "instance_id": "12345", - "relationship": "componentOf" - } - ], - "assertions": [ - { - "label": "org.test.assertion", - "data": "assertion" - } - ] - }) - .to_string() -} - -fn parent_json() -> String { - json!({ - "title": "Parent Test", - "format": "image/jpeg", - "instance_id": "12345", - "relationship": "parentOf" - }) - .to_string() -} diff --git a/cawg_identity/src/tests/fixtures/manifest_json.rs b/cawg_identity/src/tests/fixtures/manifest_json.rs new file mode 100644 index 000000000..957cd9a99 --- /dev/null +++ b/cawg_identity/src/tests/fixtures/manifest_json.rs @@ -0,0 +1,65 @@ +// Copyright 2025 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 serde_json::json; + +/// Boilerplate JSON to use when building manifests for test cases. +pub(crate) fn manifest_json() -> String { + json!({ + "vendor": "test", + "claim_generator_info": [ + { + "name": "c2pa_test", + "version": "1.0.0" + } + ], + "metadata": [ + { + "dateTime": "1985-04-12T23:20:50.52Z", + "my_custom_metadata": "my custom metatdata value" + } + ], + "title": "Test_Manifest", + "format": "image/tiff", + "instance_id": "1234", + "thumbnail": { + "format": "image/jpeg", + "identifier": "thumbnail.jpg" + }, + "ingredients": [ + { + "title": "Test", + "format": "image/jpeg", + "instance_id": "12345", + "relationship": "componentOf" + } + ], + "assertions": [ + { + "label": "org.test.assertion", + "data": "assertion" + } + ] + }) + .to_string() +} + +pub(crate) fn parent_json() -> String { + json!({ + "title": "Parent Test", + "format": "image/jpeg", + "instance_id": "12345", + "relationship": "parentOf" + }) + .to_string() +} diff --git a/cawg_identity/src/tests/fixtures/mod.rs b/cawg_identity/src/tests/fixtures/mod.rs index 431010546..969ab7fe9 100644 --- a/cawg_identity/src/tests/fixtures/mod.rs +++ b/cawg_identity/src/tests/fixtures/mod.rs @@ -13,6 +13,9 @@ #![allow(unused)] +mod manifest_json; +pub(crate) use manifest_json::{manifest_json, parent_json}; + mod naive_credential_holder; pub(crate) use naive_credential_holder::{ NaiveAsyncCredentialHolder, NaiveCredentialHolder, NaiveSignatureVerifier, diff --git a/cawg_identity/src/tests/fixtures/validation_errors/malformed_cbor.jpg b/cawg_identity/src/tests/fixtures/validation_errors/malformed_cbor.jpg new file mode 100644 index 000000000..a897f431d Binary files /dev/null and b/cawg_identity/src/tests/fixtures/validation_errors/malformed_cbor.jpg differ diff --git a/cawg_identity/src/tests/identity_assertion/mod.rs b/cawg_identity/src/tests/identity_assertion/mod.rs index f7cceb843..6b5291944 100644 --- a/cawg_identity/src/tests/identity_assertion/mod.rs +++ b/cawg_identity/src/tests/identity_assertion/mod.rs @@ -12,3 +12,4 @@ // each license. mod signer_payload; +mod validation_errors; diff --git a/cawg_identity/src/tests/identity_assertion/validation_errors.rs b/cawg_identity/src/tests/identity_assertion/validation_errors.rs new file mode 100644 index 000000000..d20181f82 --- /dev/null +++ b/cawg_identity/src/tests/identity_assertion/validation_errors.rs @@ -0,0 +1,61 @@ +// Copyright 2025 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::io::Cursor; + +use c2pa::Reader; +use c2pa_status_tracker::{LogKind, StatusTracker}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::wasm_bindgen_test; + +use crate::IdentityAssertion; + +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test +)] +#[cfg_attr(target_os = "wasi", wstd::test)] +async fn malformed_cbor() { + let format = "image/jpeg"; + let test_image = include_bytes!("../fixtures/validation_errors/malformed_cbor.jpg"); + + let mut test_image = Cursor::new(test_image); + + // Initial read with default `Reader` should pass without issues. + let reader = Reader::from_stream(format, &mut test_image).unwrap(); + assert_eq!(reader.validation_status(), None); + + // Re-parse with identity assertion code should find malformed CBOR error. + let mut status_tracker = StatusTracker::default(); + + let active_manifest = reader.active_manifest().unwrap(); + let ia_results: Vec> = + IdentityAssertion::from_manifest(active_manifest, &mut status_tracker).collect(); + + assert_eq!(ia_results.len(), 1); + + let ia_err = ia_results[0].as_ref().unwrap_err(); + assert_eq!(ia_err.to_string(), "could not decode assertion cawg.identity (version (no version), content type application/json): missing field `signer_payload`"); + + assert_eq!(status_tracker.logged_items().len(), 1); + + let log = &status_tracker.logged_items()[0]; + assert_eq!(log.kind, LogKind::Failure); + assert_eq!(log.label, "cawg.identity"); + assert_eq!(log.description, "invalid CBOR"); + assert_eq!( + log.validation_status.as_ref().unwrap().as_ref(), + "cawg.identity.cbor.invalid" + ); +} diff --git a/sdk/src/error.rs b/sdk/src/error.rs index 49d4892ff..eccaa49d8 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -46,6 +46,9 @@ pub enum Error { #[error("could not find the assertion to redact")] AssertionRedactionNotFound, + #[error("assertion-specific error: {0}")] + AssertionSpecificError(String), + #[error("bad parameter: {0}")] BadParam(String),