diff --git a/cawgi.jpg b/cawgi.jpg new file mode 100644 index 000000000..eee5d4987 Binary files /dev/null and b/cawgi.jpg differ diff --git a/sdk/examples/cawg_identity.rs b/sdk/examples/cawg_identity.rs index 01f9b28aa..c7f9663b0 100644 --- a/sdk/examples/cawg_identity.rs +++ b/sdk/examples/cawg_identity.rs @@ -29,7 +29,6 @@ mod cawg { crypto::raw_signature, identity::{ builder::{AsyncIdentityAssertionBuilder, AsyncIdentityAssertionSigner}, - validator::CawgValidator, x509::AsyncX509CredentialHolder, }, AsyncSigner, Builder, Reader, SigningAlg, @@ -125,10 +124,8 @@ mod cawg { builder.sign_file_async(&signer, source, &dest).await?; - let mut reader = Reader::from_file(dest)?; - - reader.post_validate_async(&CawgValidator {}).await?; - + //let reader = Reader::from_file_async(dest).await?; + let reader = Reader::from_file(dest)?; println!("{reader}"); Ok(()) } diff --git a/sdk/src/identity/identity_assertion/assertion.rs b/sdk/src/identity/identity_assertion/assertion.rs index 7935ba850..64303739c 100644 --- a/sdk/src/identity/identity_assertion/assertion.rs +++ b/sdk/src/identity/identity_assertion/assertion.rs @@ -93,6 +93,10 @@ impl IdentityAssertion { ia.label = Some(to_assertion_uri(manifest_label, a.label())); } } + // TO DO: Add error readout if the proposed new setting resulted + // in this assertion being parsed and converted to JSON. This function + // has become incompatible with the now-default behavior to validate + // identity assertions during parsing. (a.label().to_owned(), ia) }) .inspect(|(label, r)| { diff --git a/sdk/src/identity/validator.rs b/sdk/src/identity/validator.rs index 5400aa51f..a20497486 100644 --- a/sdk/src/identity/validator.rs +++ b/sdk/src/identity/validator.rs @@ -61,7 +61,6 @@ mod tests { #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use wasm_bindgen_test::wasm_bindgen_test; - use super::*; use crate::{Reader, ValidationState}; const CONNECTED_IDENTITIES_VALID: &[u8] = @@ -78,9 +77,13 @@ mod tests { crate::settings::set_settings_value("verify.verify_trust", false).unwrap(); let mut stream = Cursor::new(CONNECTED_IDENTITIES_VALID); - let mut reader = Reader::from_stream("image/jpeg", &mut stream).unwrap(); - reader.post_validate_async(&CawgValidator {}).await.unwrap(); + + let reader = Reader::from_stream_async("image/jpeg", &mut stream) + .await + .unwrap(); + //println!("validation results: {}", reader); + assert_eq!( reader .validation_results() @@ -100,9 +103,13 @@ mod tests { crate::settings::set_settings_value("verify.verify_trust", false).unwrap(); let mut stream = Cursor::new(MULTIPLE_IDENTITIES_VALID); - let mut reader = Reader::from_stream("image/jpeg", &mut stream).unwrap(); - reader.post_validate_async(&CawgValidator {}).await.unwrap(); + + let reader = Reader::from_stream_async("image/jpeg", &mut stream) + .await + .unwrap(); + println!("validation results: {reader}"); + assert_eq!( reader .validation_results() @@ -116,10 +123,13 @@ mod tests { } #[c2pa_test_async] - async fn test_post_validate_with_hard_binding_missing() { + async fn test_cawg_validate_with_hard_binding_missing() { let mut stream = Cursor::new(NO_HARD_BINDING); - let mut reader = Reader::from_stream("image/jpeg", &mut stream).unwrap(); - reader.post_validate_async(&CawgValidator {}).await.unwrap(); + + let reader = Reader::from_stream_async("image/jpeg", &mut stream) + .await + .unwrap(); + assert_eq!( reader .validation_results() diff --git a/sdk/src/identity/x509/async_x509_credential_holder.rs b/sdk/src/identity/x509/async_x509_credential_holder.rs index 15f7ab7c9..24efac9e8 100644 --- a/sdk/src/identity/x509/async_x509_credential_holder.rs +++ b/sdk/src/identity/x509/async_x509_credential_holder.rs @@ -164,6 +164,9 @@ mod tests { // Read back the Manifest that was generated. dest.rewind().unwrap(); + // TO DO: Retool this test to use Reader::from_stream_async and add a setting + // to parse (convert to JSON) or not parse (leave usable with iteration pattern + // below) the identity assertions. let manifest_store = Reader::from_stream(format, &mut dest).unwrap(); assert_eq!(manifest_store.validation_status(), None); diff --git a/sdk/src/identity/x509/x509_credential_holder.rs b/sdk/src/identity/x509/x509_credential_holder.rs index ff59193fc..43b6e2a5f 100644 --- a/sdk/src/identity/x509/x509_credential_holder.rs +++ b/sdk/src/identity/x509/x509_credential_holder.rs @@ -88,7 +88,7 @@ mod tests { x509::{X509CredentialHolder, X509SignatureVerifier}, IdentityAssertion, }, - status_tracker::StatusTracker, + status_tracker::{LogKind, StatusTracker}, Builder, Reader, SigningAlg, }; @@ -134,9 +134,31 @@ mod tests { // Read back the Manifest that was generated. dest.rewind().unwrap(); - let manifest_store = Reader::from_stream(format, &mut dest).unwrap(); + // TO DO: Retool this test to use Reader::from_stream (NOT async) and add a setting + // to parse (convert to JSON) or not parse (leave usable with iteration pattern + // below) the identity assertions. + let manifest_store = Reader::from_stream_async(format, &mut dest).await.unwrap(); + assert_eq!(manifest_store.validation_status(), None); + let validation_results = manifest_store.validation_results().unwrap(); + let active_manifest_results = validation_results.active_manifest().unwrap(); + let active_manifest_success_codes = active_manifest_results.success(); + + println!("{manifest_store}"); + + let mut ia_success_codes = active_manifest_success_codes.iter().filter(|s| { + s.url() + .map(|url| url.ends_with("cawg.identity")) + .unwrap_or(false) + && !s.code().starts_with("assertion.") + }); + + let ia_success = ia_success_codes.next().unwrap(); + assert_eq!(ia_success.code(), "signingCredential.trusted"); + assert!(ia_success.url().unwrap().ends_with("cawg.identity")); + assert_eq!(ia_success.kind(), &LogKind::Success); + let manifest = manifest_store.active_manifest().unwrap(); let mut st = StatusTracker::default(); let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st); diff --git a/sdk/src/manifest.rs b/sdk/src/manifest.rs index d8fb74fa9..1614bb4d9 100644 --- a/sdk/src/manifest.rs +++ b/sdk/src/manifest.rs @@ -30,10 +30,12 @@ use crate::{ crypto::raw_signature::SigningAlg, error::{Error, Result}, hashed_uri::HashedUri, + identity::IdentityAssertion, ingredient::Ingredient, jumbf::labels::{to_absolute_uri, to_assertion_uri}, manifest_assertion::ManifestAssertion, resource_store::{mime_from_uri, skip_serializing_resources, ResourceRef, ResourceStore}, + status_tracker::StatusTracker, store::Store, ClaimGeneratorInfo, ManifestAssertionKind, }; @@ -350,6 +352,7 @@ impl Manifest { store: &Store, manifest_label: &str, options: &mut StoreOptions, + validation_log: &mut StatusTracker, ) -> Result { let claim = store .get_claim(manifest_label) @@ -535,6 +538,44 @@ impl Manifest { .set_instance(claim_assertion.instance()); manifest.assertions.push(manifest_assertion); } + label if label == "cawg.identity" || label.starts_with("cawg.identity__") => { + let value = assertion.as_json_object()?; + let mut ma = ManifestAssertion::new(label.to_string(), value) + .set_instance(claim_assertion.instance()); + + //dbg!(&identity_assertion); + + let mut partial_claim = crate::dynamic_assertion::PartialClaim::default(); + for a in claim.assertions() { + partial_claim.add_assertion(a); + } + + let uri = to_assertion_uri(manifest_label, label); + validation_log.push_current_uri(&uri); + let value: Option = if _sync { + crate::log_item!( + uri, + "formatting not supported in sync", + "from_store - validating cawg.identity" + ) + .validation_status("cawg.validation_skipped") + .informational(validation_log); + None + } else { + let identity_assertion: IdentityAssertion = ma.to_assertion()?; + identity_assertion + .validate_partial_claim(&partial_claim, validation_log) + .await + .ok() + }; + if let Some(v) = value { + //debug!("cawg.identity validation returned: {v}"); + ma = ManifestAssertion::new(label.to_string(), v) + .set_instance(claim_assertion.instance()); + } + validation_log.pop_current_uri(); + manifest.assertions.push(ma); + } _ => { // inject assertions for all other assertions match assertion.decode_data() { diff --git a/sdk/src/manifest_store_report.rs b/sdk/src/manifest_store_report.rs index 0f101653b..dbc4b4f3b 100644 --- a/sdk/src/manifest_store_report.rs +++ b/sdk/src/manifest_store_report.rs @@ -147,6 +147,7 @@ impl ManifestReport { json = b64_tag(json, "hash"); json = omit_tag(json, "pad"); + json = omit_tag(json, "pad1"); json } diff --git a/sdk/src/reader.rs b/sdk/src/reader.rs index 4852bd380..1b18512b8 100644 --- a/sdk/src/reader.rs +++ b/sdk/src/reader.rs @@ -36,14 +36,14 @@ use crate::{ dynamic_assertion::PartialClaim, error::{Error, Result}, jumbf::labels::{manifest_label_from_uri, to_absolute_uri, to_relative_uri}, - jumbf_io, + jumbf_io, log_item, manifest::StoreOptions, manifest_store_report::ManifestStoreReport, settings::get_settings_value, status_tracker::StatusTracker, store::Store, validation_results::{ValidationResults, ValidationState}, - validation_status::ValidationStatus, + validation_status::{ValidationStatus, ASSERTION_MISSING, ASSERTION_NOT_REDACTED}, Manifest, ManifestAssertion, }; @@ -108,13 +108,17 @@ type ValidationFn = impl Reader { /// Create a manifest store [`Reader`] from a stream. A Reader is used to validate C2PA data from an asset. + /// /// # Arguments /// * `format` - The format of the stream. MIME type or extension that maps to a MIME type. /// * `stream` - The stream to read from. Must implement the Read and Seek traits. (NOTE: Explain Send trait, required for both sync & async?). + /// /// # Returns /// A [`Reader`] for the manifest store. + /// /// # Errors /// Returns an [`Error`] when the manifest data cannot be read. If there's no error upon reading, you must still check validation status to ensure that the manifest data is validated. That is, even if there are no errors, the data still might not be valid. + /// /// # Example /// This example reads from a memory buffer and prints out the JSON manifest data. /// ```no_run @@ -125,21 +129,29 @@ impl Reader { /// let reader = Reader::from_stream("image/jpeg", stream).unwrap(); /// println!("{}", reader.json()); /// ``` - #[async_generic()] + /// + /// # Note + /// [CAWG identity] assertions require async calls for validation. + #[async_generic] #[cfg(not(target_arch = "wasm32"))] pub fn from_stream(format: &str, mut stream: impl Read + Seek + Send) -> Result { let verify = get_settings_value::("verify.verify_after_reading")?; // defaults to true let mut validation_log = StatusTracker::default(); + let store = if _sync { Store::from_stream(format, &mut stream, verify, &mut validation_log) } else { Store::from_stream_async(format, &mut stream, verify, &mut validation_log).await }?; - Self::from_store(store, &validation_log) + if _sync { + Self::from_store(store, &mut validation_log) + } else { + Self::from_store_async(store, &mut validation_log).await + } } - #[async_generic()] + #[async_generic] #[cfg(target_arch = "wasm32")] pub fn from_stream(format: &str, mut stream: impl Read + Seek) -> Result { let verify = get_settings_value::("verify.verify_after_reading")?; // defaults to true @@ -151,26 +163,38 @@ impl Reader { Store::from_stream_async(format, &mut stream, verify, &mut validation_log).await }?; - Self::from_store(store, &validation_log) + if _sync { + Self::from_store(store, &mut validation_log) + } else { + Self::from_store_async(store, &mut validation_log).await + } } #[cfg(feature = "file_io")] /// Create a manifest store [`Reader`] from a file. /// If the `fetch_remote_manifests` feature is enabled, and the asset refers to a remote manifest, the function fetches a remote manifest. + /// /// NOTE: If the file does not have a manifest store, the function will check for a sidecar manifest with the same base file name and a .c2pa extension. + /// /// # Arguments /// * `path` - The path to the file. + /// /// # Returns /// A [`Reader`] for the manifest store. + /// /// # Errors /// Returns an [`Error`] when the manifest data cannot be read from the specified file. If there's no error upon reading, you must still check validation status to ensure that the manifest data is validated. That is, even if there are no errors, the data still might not be valid. + /// /// # Example - /// This example + /// /// ```no_run /// use c2pa::Reader; /// let reader = Reader::from_file("path/to/file.jpg").unwrap(); /// ``` - #[async_generic()] + /// + /// # Note + /// [CAWG identity] assertions require async calls for validation. + #[async_generic] pub fn from_file>(path: P) -> Result { let path = path.as_ref(); let format = crate::format_from_path(path).ok_or(crate::Error::UnsupportedType)?; @@ -255,7 +279,7 @@ impl Reader { .await }?; - Self::from_store(store, &validation_log) + Self::from_store(store, &mut validation_log) } /// Create a [`Reader`] from an initial segment and a fragment stream. @@ -294,7 +318,7 @@ impl Reader { .await }?; - Self::from_store(store, &validation_log) + Self::from_store(store, &mut validation_log) } #[cfg(feature = "file_io")] @@ -321,7 +345,7 @@ impl Reader { verify, &mut validation_log, ) { - Ok(store) => Self::from_store(store, &validation_log), + Ok(store) => Self::from_store(store, &mut validation_log), Err(e) => Err(e), } } @@ -677,9 +701,7 @@ impl Reader { } #[async_generic()] - fn from_store(store: Store, validation_log: &StatusTracker) -> Result { - let mut validation_results = ValidationResults::from_store(&store, validation_log); - + fn from_store(store: Store, validation_log: &mut StatusTracker) -> Result { let active_manifest = store.provenance_label(); let mut manifests = HashMap::new(); let mut options = StoreOptions::default(); @@ -687,20 +709,29 @@ impl Reader { for claim in store.claims() { let manifest_label = claim.label(); let result = if _sync { - Manifest::from_store(&store, manifest_label, &mut options) + Manifest::from_store(&store, manifest_label, &mut options, validation_log) } else { - Manifest::from_store_async(&store, manifest_label, &mut options).await + Manifest::from_store_async(&store, manifest_label, &mut options, validation_log) + .await }; + match result { Ok(manifest) => { manifests.insert(manifest_label.to_owned(), manifest); } Err(e) => { - validation_results.add_status(ValidationStatus::from_error(&e)); + let uri = crate::jumbf::labels::to_manifest_uri(manifest_label); + let code = ValidationStatus::code_from_error(&e); + log_item!(uri.clone(), "Failed to load manifest", "Reader::from_store") + .validation_status(code) + .failure(validation_log, Error::C2PAValidation(e.to_string()))?; + //validation_log.add_failure(ValidationStatus::from_error(&e)); } }; } + let validation_results = ValidationResults::from_store(&store, validation_log); + // resolve redactions // Even though we validate // compare options.redacted_assertions and options.missing_assertions @@ -715,15 +746,16 @@ impl Reader { // Add any remaining redacted assertions to the validation results // todo: figure out what to do here! - if !redacted.is_empty() { - eprintln!("Not Redacted: {redacted:?}"); - return Err(Error::AssertionRedactionNotFound); + for uri in &redacted { + log_item!(uri.clone(), "assertion not redacted", "Reader::from_store") + .validation_status(ASSERTION_NOT_REDACTED) + .informational(validation_log); } - if !missing.is_empty() { - eprintln!("Assertion Missing: {missing:?}"); - return Err(Error::AssertionMissing { - url: redacted[0].to_owned(), - }); + + for uri in &missing { + log_item!(uri.clone(), "assertion missing", "Reader::from_store") + .validation_status(ASSERTION_MISSING) + .informational(validation_log); } let validation_state = validation_results.validation_state(); diff --git a/sdk/src/validation_status.rs b/sdk/src/validation_status.rs index 8f03bcfd1..dbae60fc2 100644 --- a/sdk/src/validation_status.rs +++ b/sdk/src/validation_status.rs @@ -150,7 +150,7 @@ impl ValidationStatus { } // Maps errors into validation_status codes. - fn code_from_error(error: &Error) -> &str { + pub(crate) fn code_from_error(error: &Error) -> &'static str { match error { Error::ClaimMissing { .. } => CLAIM_MISSING, Error::AssertionMissing { .. } => ASSERTION_MISSING, @@ -163,6 +163,7 @@ impl ValidationStatus { } /// Creates a ValidationStatus from an error code. + #[allow(dead_code)] pub(crate) fn from_error(error: &Error) -> Self { // We need to create error codes here for client processing. let code = Self::code_from_error(error);