From 41d19ae1a6ec8e22fb3084c719a2400b4d3ac8a1 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Tue, 5 Aug 2025 13:35:50 -0400 Subject: [PATCH 01/10] TDD: Test that expects a success code for identity assertion but doesn't find it yet --- .../identity/x509/x509_credential_holder.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sdk/src/identity/x509/x509_credential_holder.rs b/sdk/src/identity/x509/x509_credential_holder.rs index 2b2dcd8ff..9346db23f 100644 --- a/sdk/src/identity/x509/x509_credential_holder.rs +++ b/sdk/src/identity/x509/x509_credential_holder.rs @@ -141,6 +141,26 @@ mod tests { let manifest_store = Reader::from_stream(format, &mut dest).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(); + + dbg!(&active_manifest_success_codes); + + 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(); + dbg!(&ia_success); + + if true { + panic!("Look for identity assertion success codes"); + } + let manifest = manifest_store.active_manifest().unwrap(); let mut st = StatusTracker::default(); let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st); From ac8acc18f31108b7696d0a803a475196782976fb Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Tue, 2 Sep 2025 15:52:42 -0700 Subject: [PATCH 02/10] Sketch in proposed changes for CAWG validation in Reader interface --- c2pa_c_ffi/src/c_api.rs | 4 ++++ c2pa_c_ffi/src/json_api.rs | 3 +++ cli/src/main.rs | 3 +++ sdk/examples/cawg_identity.rs | 4 +--- sdk/src/identity/validator.rs | 11 ++++------- sdk/src/reader.rs | 20 ++++++++++++++++++-- 6 files changed, 33 insertions(+), 12 deletions(-) diff --git a/c2pa_c_ffi/src/c_api.rs b/c2pa_c_ffi/src/c_api.rs index eda74dc67..5855e49aa 100644 --- a/c2pa_c_ffi/src/c_api.rs +++ b/c2pa_c_ffi/src/c_api.rs @@ -463,6 +463,10 @@ pub unsafe extern "C" fn c2pa_free_string_array(ptr: *const *const c_char, count // Run CAWG post-validation - this is async and requires a runtime. fn post_validate(result: Result) -> Result { + if true { + todo!("Remove me?"); + } + match result { Ok(mut reader) => { let runtime = match Runtime::new() { diff --git a/c2pa_c_ffi/src/json_api.rs b/c2pa_c_ffi/src/json_api.rs index a53a5cd91..5a976232a 100644 --- a/c2pa_c_ffi/src/json_api.rs +++ b/c2pa_c_ffi/src/json_api.rs @@ -24,6 +24,9 @@ use crate::{Error, Result, SignerInfo}; pub fn read_file(path: &str, data_dir: Option) -> Result { let mut reader = Reader::from_file(path).map_err(Error::from_c2pa_error)?; let runtime = Runtime::new().map_err(|e| Error::Other(e.to_string()))?; + if true { + todo!("Remove post_validate_async here?"); + } runtime .block_on(reader.post_validate_async(&CawgValidator {})) .map_err(Error::from_c2pa_error)?; diff --git a/cli/src/main.rs b/cli/src/main.rs index 7196b95a4..f56139d33 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -574,6 +574,9 @@ fn verify_fragmented(init_pattern: &Path, frag_pattern: &Path) -> Result Result<()> { + if true { + todo!("Remove me?"); + } #[cfg(not(target_os = "wasi"))] { Runtime::new()? diff --git a/sdk/examples/cawg_identity.rs b/sdk/examples/cawg_identity.rs index 01f9b28aa..9ffd91054 100644 --- a/sdk/examples/cawg_identity.rs +++ b/sdk/examples/cawg_identity.rs @@ -125,9 +125,7 @@ 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?; println!("{reader}"); Ok(()) diff --git a/sdk/src/identity/validator.rs b/sdk/src/identity/validator.rs index 5400aa51f..b4fb3a6fb 100644 --- a/sdk/src/identity/validator.rs +++ b/sdk/src/identity/validator.rs @@ -78,8 +78,7 @@ 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 @@ -100,8 +99,7 @@ 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 @@ -116,10 +114,9 @@ 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/reader.rs b/sdk/src/reader.rs index a14dbb639..5df93f815 100644 --- a/sdk/src/reader.rs +++ b/sdk/src/reader.rs @@ -136,7 +136,13 @@ impl Reader { Store::from_stream_async(format, &mut stream, verify, &mut validation_log).await }?; - Self::from_store(store, &validation_log) + let /* mut */ result = Self::from_store(store, &validation_log)?; + if false { + // QUESTION: What to do if we're in the _sync version and there + // are identity assertions? Just report an error (needs async)? + todo!("Add identity assertion validation here"); + } + Ok(result) } #[async_generic()] @@ -151,7 +157,11 @@ impl Reader { Store::from_stream_async(format, &mut stream, verify, &mut validation_log).await }?; - Self::from_store(store, &validation_log) + let mut result = Self::from_store(store, &validation_log)?; + if false { + todo!("Add identity assertion validation here"); + } + Ok(result) } #[cfg(feature = "file_io")] @@ -735,6 +745,9 @@ impl Reader { validator: &impl AsyncPostValidator ))] pub fn post_validate(&mut self, validator: &impl PostValidator) -> Result<()> { + if true { + todo!("Remove me"); + } let mut validation_log = StatusTracker::default(); let mut validation_results = self.validation_results.take().unwrap_or_default(); let mut assertion_values = HashMap::new(); @@ -997,6 +1010,9 @@ pub mod tests { #[test] fn test_reader_post_validate() -> Result<()> { + if true { + todo!("Remove me"); + } use crate::{log_item, status_tracker::StatusTracker}; let mut reader = From d8c04e35e6016be00e38bfaacda0f1d44afaceb3 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Fri, 5 Sep 2025 11:23:41 -0700 Subject: [PATCH 03/10] Remove unused import --- sdk/src/identity/validator.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sdk/src/identity/validator.rs b/sdk/src/identity/validator.rs index b4fb3a6fb..f79ca7231 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,7 +77,9 @@ mod tests { crate::settings::set_settings_value("verify.verify_trust", false).unwrap(); let mut stream = Cursor::new(CONNECTED_IDENTITIES_VALID); - let reader = Reader::from_stream_async("image/jpeg", &mut stream).await.unwrap(); + let reader = Reader::from_stream_async("image/jpeg", &mut stream) + .await + .unwrap(); //println!("validation results: {}", reader); assert_eq!( reader @@ -99,7 +100,9 @@ mod tests { crate::settings::set_settings_value("verify.verify_trust", false).unwrap(); let mut stream = Cursor::new(MULTIPLE_IDENTITIES_VALID); - let reader = Reader::from_stream_async("image/jpeg", &mut stream).await.unwrap(); + let reader = Reader::from_stream_async("image/jpeg", &mut stream) + .await + .unwrap(); println!("validation results: {reader}"); assert_eq!( reader @@ -116,7 +119,9 @@ mod tests { #[c2pa_test_async] async fn test_cawg_validate_with_hard_binding_missing() { let mut stream = Cursor::new(NO_HARD_BINDING); - let reader = Reader::from_stream_async("image/jpeg", &mut stream).await.unwrap(); + let reader = Reader::from_stream_async("image/jpeg", &mut stream) + .await + .unwrap(); assert_eq!( reader .validation_results() From faf671b7b561f7579d48f686774c1896d53efc15 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Thu, 11 Sep 2025 16:07:29 -0700 Subject: [PATCH 04/10] Getting back into this project; a little more clarity on what is needed and where --- sdk/src/reader.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/sdk/src/reader.rs b/sdk/src/reader.rs index 5df93f815..9d14d8693 100644 --- a/sdk/src/reader.rs +++ b/sdk/src/reader.rs @@ -137,11 +137,18 @@ impl Reader { }?; let /* mut */ result = Self::from_store(store, &validation_log)?; - if false { - // QUESTION: What to do if we're in the _sync version and there - // are identity assertions? Just report an error (needs async)? - todo!("Add identity assertion validation here"); + if _sync { + // TO DO: Figure out how to handle synchronous validation with + // identity assertions? Just report an error (needs async)? + if false { + todo!("Add identity assertion validation here"); + } + } else { + if true { + todo!("Add identity assertion validation here"); + } } + Ok(result) } From 0f90e210bbd5b2ad1f6af752ef45633ecc6f969c Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Thu, 11 Sep 2025 16:46:44 -0700 Subject: [PATCH 05/10] Clean up test noise now that I know where I need to place updates --- c2pa_c_ffi/src/c_api.rs | 3 +- c2pa_c_ffi/src/json_api.rs | 3 +- cli/src/main.rs | 3 +- sdk/examples/cawg_identity.rs | 1 - .../identity/x509/x509_credential_holder.rs | 10 +++--- sdk/src/reader.rs | 34 ++++++++++++++----- 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/c2pa_c_ffi/src/c_api.rs b/c2pa_c_ffi/src/c_api.rs index 5855e49aa..77f032ec9 100644 --- a/c2pa_c_ffi/src/c_api.rs +++ b/c2pa_c_ffi/src/c_api.rs @@ -463,7 +463,8 @@ pub unsafe extern "C" fn c2pa_free_string_array(ptr: *const *const c_char, count // Run CAWG post-validation - this is async and requires a runtime. fn post_validate(result: Result) -> Result { - if true { + if false { + // CONSIDER BEFORE MERGING ... todo!("Remove me?"); } diff --git a/c2pa_c_ffi/src/json_api.rs b/c2pa_c_ffi/src/json_api.rs index 5a976232a..1bfdeffa9 100644 --- a/c2pa_c_ffi/src/json_api.rs +++ b/c2pa_c_ffi/src/json_api.rs @@ -24,7 +24,8 @@ use crate::{Error, Result, SignerInfo}; pub fn read_file(path: &str, data_dir: Option) -> Result { let mut reader = Reader::from_file(path).map_err(Error::from_c2pa_error)?; let runtime = Runtime::new().map_err(|e| Error::Other(e.to_string()))?; - if true { + if false { + // CONSIDER BEFORE MERGING ... todo!("Remove post_validate_async here?"); } runtime diff --git a/cli/src/main.rs b/cli/src/main.rs index 530c4fa49..bc05b4319 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -574,7 +574,8 @@ fn verify_fragmented(init_pattern: &Path, frag_pattern: &Path) -> Result Result<()> { - if true { + if false { + // CONSIDER BEFORE MERGING ... todo!("Remove me?"); } #[cfg(not(target_os = "wasi"))] diff --git a/sdk/examples/cawg_identity.rs b/sdk/examples/cawg_identity.rs index 9ffd91054..1803f7cf6 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, diff --git a/sdk/src/identity/x509/x509_credential_holder.rs b/sdk/src/identity/x509/x509_credential_holder.rs index 2240ef25d..2aed828af 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,7 +134,7 @@ mod tests { // Read back the Manifest that was generated. dest.rewind().unwrap(); - let manifest_store = Reader::from_stream(format, &mut dest).unwrap(); + 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(); @@ -153,9 +153,9 @@ mod tests { let ia_success = ia_success_codes.next().unwrap(); dbg!(&ia_success); - if true { - panic!("Look for identity assertion success codes"); - } + 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(); diff --git a/sdk/src/reader.rs b/sdk/src/reader.rs index 9d14d8693..2c081e66b 100644 --- a/sdk/src/reader.rs +++ b/sdk/src/reader.rs @@ -136,7 +136,8 @@ impl Reader { Store::from_stream_async(format, &mut stream, verify, &mut validation_log).await }?; - let /* mut */ result = Self::from_store(store, &validation_log)?; + #[allow(unused_mut)] // TEMPORARY until I figure out the synchronous path. + let mut result = Self::from_store(store, &validation_log)?; if _sync { // TO DO: Figure out how to handle synchronous validation with // identity assertions? Just report an error (needs async)? @@ -144,9 +145,10 @@ impl Reader { todo!("Add identity assertion validation here"); } } else { - if true { - todo!("Add identity assertion validation here"); - } + use crate::identity::validator::CawgValidator; + result + .post_validate_internal_async(&CawgValidator {}) + .await?; } Ok(result) @@ -752,9 +754,24 @@ impl Reader { validator: &impl AsyncPostValidator ))] pub fn post_validate(&mut self, validator: &impl PostValidator) -> Result<()> { - if true { - todo!("Remove me"); + if false { + // CONSIDER BEFORE MERGING ... + todo!("Remove me?"); + } + + if _sync { + self.post_validate_internal(validator) + } else { + self.post_validate_internal_async(validator).await } + } + + #[async_generic(async_signature( + &mut self, + validator: &impl AsyncPostValidator + ))] + fn post_validate_internal(&mut self, validator: &impl PostValidator) -> Result<()> { + // TEMPORARY: Make this available while I sort out new code path. let mut validation_log = StatusTracker::default(); let mut validation_results = self.validation_results.take().unwrap_or_default(); let mut assertion_values = HashMap::new(); @@ -1017,8 +1034,9 @@ pub mod tests { #[test] fn test_reader_post_validate() -> Result<()> { - if true { - todo!("Remove me"); + if false { + // CONSIDER BEFORE MERGING ... + todo!("Remove me?"); } use crate::{log_item, status_tracker::StatusTracker}; From 5270494a8d834afd89fb25c095b28fe19551a5ef Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Fri, 12 Sep 2025 14:18:27 -0700 Subject: [PATCH 06/10] =?UTF-8?q?Hmmm=20=E2=80=A6=20maybe=20this=20doesn't?= =?UTF-8?q?=20need=20to=20be=20so=20complicated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- c2pa_c_ffi/src/c_api.rs | 5 - c2pa_c_ffi/src/json_api.rs | 4 - cli/src/main.rs | 4 - sdk/examples/cawg_identity.rs | 3 +- sdk/src/identity/validator.rs | 14 +- .../identity/x509/x509_credential_holder.rs | 9 +- sdk/src/reader.rs | 218 ++++++++++++++---- 7 files changed, 188 insertions(+), 69 deletions(-) diff --git a/c2pa_c_ffi/src/c_api.rs b/c2pa_c_ffi/src/c_api.rs index 77f032ec9..eda74dc67 100644 --- a/c2pa_c_ffi/src/c_api.rs +++ b/c2pa_c_ffi/src/c_api.rs @@ -463,11 +463,6 @@ pub unsafe extern "C" fn c2pa_free_string_array(ptr: *const *const c_char, count // Run CAWG post-validation - this is async and requires a runtime. fn post_validate(result: Result) -> Result { - if false { - // CONSIDER BEFORE MERGING ... - todo!("Remove me?"); - } - match result { Ok(mut reader) => { let runtime = match Runtime::new() { diff --git a/c2pa_c_ffi/src/json_api.rs b/c2pa_c_ffi/src/json_api.rs index 1bfdeffa9..a53a5cd91 100644 --- a/c2pa_c_ffi/src/json_api.rs +++ b/c2pa_c_ffi/src/json_api.rs @@ -24,10 +24,6 @@ use crate::{Error, Result, SignerInfo}; pub fn read_file(path: &str, data_dir: Option) -> Result { let mut reader = Reader::from_file(path).map_err(Error::from_c2pa_error)?; let runtime = Runtime::new().map_err(|e| Error::Other(e.to_string()))?; - if false { - // CONSIDER BEFORE MERGING ... - todo!("Remove post_validate_async here?"); - } runtime .block_on(reader.post_validate_async(&CawgValidator {})) .map_err(Error::from_c2pa_error)?; diff --git a/cli/src/main.rs b/cli/src/main.rs index bc05b4319..fa0153629 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -574,10 +574,6 @@ fn verify_fragmented(init_pattern: &Path, frag_pattern: &Path) -> Result Result<()> { - if false { - // CONSIDER BEFORE MERGING ... - todo!("Remove me?"); - } #[cfg(not(target_os = "wasi"))] { Runtime::new()? diff --git a/sdk/examples/cawg_identity.rs b/sdk/examples/cawg_identity.rs index 1803f7cf6..4c371d00a 100644 --- a/sdk/examples/cawg_identity.rs +++ b/sdk/examples/cawg_identity.rs @@ -124,8 +124,7 @@ mod cawg { builder.sign_file_async(&signer, source, &dest).await?; - let reader = Reader::from_file_async(dest).await?; - + let reader = Reader::from_file_with_cawg_async(dest).await?; println!("{reader}"); Ok(()) } diff --git a/sdk/src/identity/validator.rs b/sdk/src/identity/validator.rs index f79ca7231..6022b488d 100644 --- a/sdk/src/identity/validator.rs +++ b/sdk/src/identity/validator.rs @@ -77,10 +77,13 @@ mod tests { crate::settings::set_settings_value("verify.verify_trust", false).unwrap(); let mut stream = Cursor::new(CONNECTED_IDENTITIES_VALID); - let reader = Reader::from_stream_async("image/jpeg", &mut stream) + + let reader = Reader::from_stream_with_cawg_async("image/jpeg", &mut stream) .await .unwrap(); + //println!("validation results: {}", reader); + assert_eq!( reader .validation_results() @@ -100,10 +103,13 @@ mod tests { crate::settings::set_settings_value("verify.verify_trust", false).unwrap(); let mut stream = Cursor::new(MULTIPLE_IDENTITIES_VALID); - let reader = Reader::from_stream_async("image/jpeg", &mut stream) + + let reader = Reader::from_stream_with_cawg_async("image/jpeg", &mut stream) .await .unwrap(); + println!("validation results: {reader}"); + assert_eq!( reader .validation_results() @@ -119,9 +125,11 @@ mod tests { #[c2pa_test_async] async fn test_cawg_validate_with_hard_binding_missing() { let mut stream = Cursor::new(NO_HARD_BINDING); - let reader = Reader::from_stream_async("image/jpeg", &mut stream) + + let reader = Reader::from_stream_with_cawg_async("image/jpeg", &mut stream) .await .unwrap(); + assert_eq!( reader .validation_results() diff --git a/sdk/src/identity/x509/x509_credential_holder.rs b/sdk/src/identity/x509/x509_credential_holder.rs index 2aed828af..80d1cd33b 100644 --- a/sdk/src/identity/x509/x509_credential_holder.rs +++ b/sdk/src/identity/x509/x509_credential_holder.rs @@ -134,15 +134,16 @@ mod tests { // Read back the Manifest that was generated. dest.rewind().unwrap(); - let manifest_store = Reader::from_stream_async(format, &mut dest).await.unwrap(); + let manifest_store = Reader::from_stream_with_cawg_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(); - dbg!(&active_manifest_success_codes); - let mut ia_success_codes = active_manifest_success_codes.iter().filter(|s| { s.url() .map(|url| url.ends_with("cawg.identity")) @@ -151,8 +152,6 @@ mod tests { }); let ia_success = ia_success_codes.next().unwrap(); - dbg!(&ia_success); - assert_eq!(ia_success.code(), "signingCredential.trusted"); assert!(ia_success.url().unwrap().ends_with("cawg.identity")); assert_eq!(ia_success.kind(), &LogKind::Success); diff --git a/sdk/src/reader.rs b/sdk/src/reader.rs index 2c081e66b..4805c0864 100644 --- a/sdk/src/reader.rs +++ b/sdk/src/reader.rs @@ -35,6 +35,7 @@ use crate::{ crypto::base64, dynamic_assertion::PartialClaim, error::{Error, Result}, + identity::validator::CawgValidator, jumbf::labels::{manifest_label_from_uri, to_absolute_uri, to_relative_uri}, jumbf_io, manifest::StoreOptions, @@ -108,13 +109,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,36 +130,45 @@ impl Reader { /// let reader = Reader::from_stream("image/jpeg", stream).unwrap(); /// println!("{}", reader.json()); /// ``` - #[async_generic()] + /// + /// # Note + /// This function does not validate [CAWG identity] assertions that may be + /// contained within any C2PA Manifests. If an async call is feasible, use + /// [from_stream_with_cawg_async()]; if not, you can construct an async runtime + /// on your own and perform the CAWG validation separately as shown in the + /// following example: + /// + /// ```no_run + /// use std::io::Cursor; + /// + /// use c2pa::{identity::validator::CawgValidator, Reader}; + /// let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); + /// let mut reader = Reader::from_stream("image/jpeg", stream).unwrap(); + /// let runtime = tokio::runtime::Runtime::new().unwrap(); + /// runtime + /// .block_on(reader.post_validate_async(&CawgValidator {})) + /// .unwrap(); + /// println!("{}", reader.json()); + /// ``` + /// + /// [CAWG identity]: https://cawg.io/identity + /// [from_stream_with_cawg_async()]: Self::from_stream_with_cawg_async + #[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 }?; - #[allow(unused_mut)] // TEMPORARY until I figure out the synchronous path. - let mut result = Self::from_store(store, &validation_log)?; - if _sync { - // TO DO: Figure out how to handle synchronous validation with - // identity assertions? Just report an error (needs async)? - if false { - todo!("Add identity assertion validation here"); - } - } else { - use crate::identity::validator::CawgValidator; - result - .post_validate_internal_async(&CawgValidator {}) - .await?; - } - - Ok(result) + Self::from_store(store, &validation_log) } - #[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 @@ -166,30 +180,112 @@ impl Reader { Store::from_stream_async(format, &mut stream, verify, &mut validation_log).await }?; - let mut result = Self::from_store(store, &validation_log)?; - if false { - todo!("Add identity assertion validation here"); + Self::from_store(store, &validation_log) + } + + /// Create a manifest store [`Reader`] from a stream. A `Reader` is used to + /// validate C2PA data from an asset. This variation also validates + /// [CAWG identity] assertions within the C2PA data. + /// + /// # 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. + /// ```ignore + /// # async fn main() { + /// use std::io::Cursor; + /// + /// use c2pa::{identity::validator::CawgValidator, Reader}; + /// + /// let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); + /// let reader = Reader::from_stream_with_cawg_async("image/jpeg", stream) + /// .await + /// .unwrap(); + /// println!("{}", reader.json()); + /// # } + /// ``` + /// + /// [CAWG identity]: https://cawg.io/identity + /// [from_stream_with_cawg_async()]: Self::from_stream_with_cawg_async + #[cfg(not(target_arch = "wasm32"))] + pub async fn from_stream_with_cawg_async( + format: &str, + stream: impl Read + Seek + Send, + ) -> Result { + let mut reader = Self::from_stream_async(format, stream).await?; + + if get_settings_value::("verify.verify_after_reading")? { + reader.post_validate_async(&CawgValidator {}).await?; + } + + Ok(reader) + } + + #[cfg(target_arch = "wasm32")] + pub async fn from_stream_with_cawg_async( + format: &str, + stream: impl Read + Seek, + ) -> Result { + let mut reader = Self::from_stream_async(format, stream).await?; + + if get_settings_value::("verify.verify_after_reading")? { + reader.post_validate_async(&CawgValidator {}).await?; } - Ok(result) + + Ok(reader) } #[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 + /// This function does not validate [CAWG identity] assertions that may be + /// contained within any C2PA Manifests. If an async call is feasible, use + /// [from_file_with_cawg_async()]; if not, you can construct an async runtime + /// on your own and perform the CAWG validation separately as shown in the + /// following example: + /// + /// ```no_run + /// use c2pa::{identity::validator::CawgValidator, Reader}; + /// + /// let mut reader = Reader::from_file("path/to/file.jpg").unwrap(); + /// let runtime = tokio::runtime::Runtime::new().unwrap(); + /// runtime + /// .block_on(reader.post_validate_async(&CawgValidator {})) + /// .unwrap(); + /// println!("{}", reader.json()); + /// ``` + /// + /// [CAWG identity]: https://cawg.io/identity + /// [from_file_with_cawg_async()]: Self::from_file_with_cawg_async + #[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)?; @@ -223,6 +319,58 @@ impl Reader { } } + #[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 + /// + /// ```no_run + /// use c2pa::Reader; + /// let reader = Reader::from_file("path/to/file.jpg").unwrap(); + /// ``` + /// + /// # Note + /// This function does not validate [CAWG identity] assertions that may be + /// contained within any C2PA Manifests. If an async call is feasible, use + /// [from_file_with_cawg_async()]; if not, you can construct an async runtime + /// on your own and perform the CAWG validation separately as shown in the + /// following example: + /// + /// ```no_run + /// use c2pa::{identity::validator::CawgValidator, Reader}; + /// + /// let mut reader = Reader::from_file("path/to/file.jpg").unwrap(); + /// let runtime = tokio::runtime::Runtime::new().unwrap(); + /// runtime + /// .block_on(reader.post_validate_async(&CawgValidator {})) + /// .unwrap(); + /// println!("{}", reader.json()); + /// ``` + /// + /// [CAWG identity]: https://cawg.io/identity + /// [from_file_with_cawg_async()]: Self::from_file_with_cawg_async + pub async fn from_file_with_cawg_async>(path: P) -> Result { + let mut reader = Self::from_file_async(path).await?; + + if get_settings_value::("verify.verify_after_reading")? { + reader.post_validate_async(&CawgValidator {}).await?; + } + + Ok(reader) + } + /// Create a manifest store [`Reader`] from a JSON string. /// # Arguments /// * `json` - A JSON string containing a manifest store definition. @@ -754,24 +902,6 @@ impl Reader { validator: &impl AsyncPostValidator ))] pub fn post_validate(&mut self, validator: &impl PostValidator) -> Result<()> { - if false { - // CONSIDER BEFORE MERGING ... - todo!("Remove me?"); - } - - if _sync { - self.post_validate_internal(validator) - } else { - self.post_validate_internal_async(validator).await - } - } - - #[async_generic(async_signature( - &mut self, - validator: &impl AsyncPostValidator - ))] - fn post_validate_internal(&mut self, validator: &impl PostValidator) -> Result<()> { - // TEMPORARY: Make this available while I sort out new code path. let mut validation_log = StatusTracker::default(); let mut validation_results = self.validation_results.take().unwrap_or_default(); let mut assertion_values = HashMap::new(); @@ -1034,10 +1164,6 @@ pub mod tests { #[test] fn test_reader_post_validate() -> Result<()> { - if false { - // CONSIDER BEFORE MERGING ... - todo!("Remove me?"); - } use crate::{log_item, status_tracker::StatusTracker}; let mut reader = From c7928b84bbaa81bf572ff00303a793a38da390aa Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Fri, 12 Sep 2025 14:53:01 -0700 Subject: [PATCH 07/10] "Fix" WASI build errors --- sdk/src/reader.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/reader.rs b/sdk/src/reader.rs index 4805c0864..05c34f265 100644 --- a/sdk/src/reader.rs +++ b/sdk/src/reader.rs @@ -272,7 +272,7 @@ impl Reader { /// on your own and perform the CAWG validation separately as shown in the /// following example: /// - /// ```no_run + /// ```ignore /// use c2pa::{identity::validator::CawgValidator, Reader}; /// /// let mut reader = Reader::from_file("path/to/file.jpg").unwrap(); @@ -348,7 +348,7 @@ impl Reader { /// on your own and perform the CAWG validation separately as shown in the /// following example: /// - /// ```no_run + /// ```ignore /// use c2pa::{identity::validator::CawgValidator, Reader}; /// /// let mut reader = Reader::from_file("path/to/file.jpg").unwrap(); From 25d029f7ca2abedd73c533403a587cce53e89d05 Mon Sep 17 00:00:00 2001 From: Gavin Peacock Date: Thu, 18 Sep 2025 23:55:32 -0700 Subject: [PATCH 08/10] feat: cawg in native Reader --- cawgi.jpg | Bin 0 -> 139724 bytes sdk/examples/cawg_identity.rs | 3 +- sdk/src/identity/validator.rs | 6 +- .../identity/x509/x509_credential_holder.rs | 6 +- sdk/src/manifest.rs | 41 ++++ sdk/src/manifest_store_report.rs | 1 + sdk/src/reader.rs | 203 +++--------------- sdk/src/validation_status.rs | 3 +- 8 files changed, 82 insertions(+), 181 deletions(-) create mode 100644 cawgi.jpg diff --git a/cawgi.jpg b/cawgi.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eee5d4987541daffa1353e8e3d2fa4c3628a43b8 GIT binary patch literal 139724 zcmeHw2|$$f*8U7Ir~_^}A}T82HYnhLsA!rjjsj{TE+JXUreH3J;FcARfd*>gQf6sD zm|CV`Xscx?nr4Nj<#KODmUcBuQ`5@w|D50Q&O4*s>;3-U{nG2}x1_VY%kP}?oaa2} z{NA~CBI-MT>qq}zIld*B}E{%2cZ`~&&C*)2_!QWfsziFwVS;@M_ zx<+|A-AlUgXQ#Td7w{>I_|yRYsY#BX%FUkKi$2*axo3LM^pqZ!pzfhv!-B%nLwW@D z4DFE;)MMPZZpqzJ$93(RY{9vnogoevzz>%^?Yff&Cr_J}o}H69W%4w9A#;TILVx-~ z#D|`tZx_a_Ugy#2vx|>KXEvd82j^tqMkXg`P7F?+n4CEY-_m)fhEB`KYx&?)n=NPV zJCXCs{*8;*x#M+H%DA)?UFM|Z@#$SAWTlVCOBokZGSgD7-Dtgb=o;5`dD%_ps&A}w zX{>i`tiN_#7p<%7?q0{;P`6H~!RBfMv?=ziAz17c$1 zh7BJv^1j4TNvUb+5s{me(q`ZhQTWH{Yt-xvP5jo)7l!JNW6L!=HVA5uTOmQ?a5Q${p0lcpDtXy z^z-E_zx+z~rN@fPKR3KDm_zU4=H}{Fhwe-7GJ|fsv75m!#Qm>j{Ht`RgTh|m- zTw3vdy|!Hw&NWTRK4A1}7gpQ;Jlz_*vzxmy+yCg!lpC|(*D;-^s~&FT+E~|Dcj2XT zPp&d01T`orty)rDp5#>=RcQ1HFqz8}Qr8D2MYl?buk^w{T17?`T08Wz)zrq6W_l)B zORH@8rb1IfK!fsxltSy`l9YqytdfdMd@qLf+x!G`*4mVO)5gTg#8PX_9z88j>7ha|+x+;1l!Jjo z4*4V}NO8ZCrIxY#ElyWVDuA{pZJH z38D@Lc*IpNHYKLajVYyrBxK-Acr~P|IORY}J}tJnRaM>_@y(1bwGpGu9{KvLl3e9?aEGd@O7~s_l-U!R&uT9yL5SZj{UYk;LdZc-1 z3>}l~wd6E;Axu_nD)ce9`UE86+ZCB~(8|P;a(#qBXVS(0W&dfNIVRyzAB34HKJj$4 z*NQ?Pb6^tgD=g#?+$*-^G(s{l}%oXi#U9xooFTr-4zl==e$l6K=O6s?a?MZoH@t{WUP;aC?emgk(W@e3B`lA~oNY zP9NYEWblaF>9x#i@XP;8m&Tx*wf2z_Pwuof#g!5`kpmePn+m<=76!l*0*4}c5M)i; z`yDDm_#u$!M$EYeL|3okl!FXNeJsG9h);#7(%gilRvY*~I;bx}fy)hnn0!B%0b2t?f=+;RyEp}}6}GAu(| zEM2R(YH_1Q(fJ0W0lreu%l0Wn%!D{P?200?07A$s--HE@BHXWpvxN^|9z?IMtc3Rl zcoZ5v0H35#B1A`=j`XHwHzigaZtoEpg9KPv zWEz?>pP?9~19oU&TzP2~c`{tJFc4toml4pwROE$Z)7dlLT#-R(2H{z#SpBa`i>@Da zKHYFo1_ZoKh0F$-Jg#n3`a0S990`U0wvL$YjM8k?4H(bsTyz4voYCm#wHOj!6O{dhx~f zC=9ezd+Gjr|JQYY^&{3Ncg{BsHU;?;%v*t7@u%xIB4_kWV$2B%vus&(tC&hgG6;*hY3OTV`!gw$n6xIS3vL@RXV)ig+t>NJ;7j zMEa`0TrclhB7+p_$eQ?nK|sx<=v&4y;WbeQD_B+pXcd9P&NmvpY|Fe5Su$zTk^n0U zy^3RoMy4XfBSGp|=49+@nH5}&01pfe2*3^iU-K{}coWSCORTjP(kd0l7McUI5SE@p zV=BXqM3~7JW6PFQCRR-7kLAbF!CR_bZ)wE40XSa>K~8DiYi<#F62+;Rv!+$%+|IaJ z;GI_xh`kX3$=NDJdIr*CMrkIzb4i(P50N@7mq$#+A-n?D32IP&fUu#Q*nTB(WTVy3 zie)$TT2gJokvlNFLu9jaVl_p9fdyU#Gz8wpM%7qu1(r4}_1t-0C*dZHUby^?Nv*6! z&L~ZYL5i zmJn~(gjoG+PSXlE0AkwqAeKjZGiU_xrPQDXKuraY3+l{bzq1Lf*H)w(I@TDdx8|8& zHF)XDc9;@fZsnIyUzJ}CXZHoJnur^VDg>vh0h47Vk7sV%NbpkA#*j=xAzIhM=vI-4 zN|2f$zs05^FhQghd^e@A|2(|r#g;vgQGW2XUd5^To!Js0OX16bl#=2z1b2f27J>KS z@L1~Fn2HQAIvhR8>hYIXo<`k;2uifo427~vP^W-+fr`O7C?cu0`4rXo!3uJak_tdH zAv>==i|}_U9l?+p&jY&GfR1tauLM@rD?-#Gwv>UL{sg5z9C}=@Vw? zI0WPB zU>QLz3L^vMxr5*e=B%=6KWi*0IapyT)o+J20b~0lU6?_<5Uv8Wc4WMIH-B86^ceIEs;Buexi^+ zh=e7At46S8T?$IdgdQ)*uq0!Uk_JzMWV@o!tGoh?1RNG7@{k<1rt(%mwax4MHY5}) zuc@YN&IB4%Ym5hw+)mma3HyUhy^1`CCS(YbIX@otIq-?_i`RfqoDV(45?cl1TU<^A zwr4^QGzKK5=EpJ$2KCf4mlz2NVg=y|VYD72y|+T?V9bG?aPBQY6-adc8frQc8~~b( z{1rr1a0XCrD)3NpP_c&Og%HGFD`F5o5PYD(*qHFez`U9SoX!-rL?llaGr&g;84W%K ztsgZc*psQ(GHao^BKjpp)~poWwJ6jfZc`3q!C3+Ep4JI9P;um6EIBh>HS)@AA2aTO zhXklK$$-RwDpEIuF%gIQAJpZcII3agV6NX{6g`yxXq^g8M!+{?4oG~+jKrW?#g;)v zfOb#I8dv=ys~GVq0&w1EI*@^NCQS_SRsr5k`4h(_@sL0Z%-zd;dsH$?(Ir(H0YcRJ z0(-Y2Y>=;@7hp-5(p7#S+?fx3aHp1{)})LhzlcxffkdIMR-h%GJ_RzD9<{8FTklB9JDP6%n4*pa`L=!R0 z01t~Cy`cq#Z9MY}D2lYgboB7}#7Z>ag!s(3Lx)8M))p`tjS+wG+UQTN+`OT01J-b+ za*V9asQ`%O;<9Qe&_w)LE0JyjdK@AlFbf$CX?|@A)pq8!$wh#qwvjs^P~#BHiM&83 zh!F*KU@8u{8g+sodNOLs5ySl2k!G4J;9WnmV$C$0TTF!f(q-f``sU{!wy&}(eb6m zzGmK=}X#HZ!Il&_@?SO_ix^*V>Z(~aSx&aE&6@boHaxQZrNGZB_fAk;;U59|-etz&b=56+Yk>1VrAIkl0Nm z6QVBqDgbi1|NNoJ1p0vVLgdW`5Y}m7mh$lxs%)rEWMV4k8J)p5P?Azcg(k$nZY_>L z9X9r9Vs20gA=r}q1?2Mwb;D!@lYKm5l5Yhv3rGXXdnN@YZf)Pd1m^9vk`;%>08y(D zl2ybEwL;X3O(}FoJRJHC*r~{~_ z0j|sSeY^#_3RdB@63rjVL3k&&j4ClI7Ev#eoo)s7t7}tKLpwKsCo(Ak{DPbUeH<@P zad50bxinY7m;q7w(@aV5AfW;G6him~*2HAMLSn9vMDwX>_!M(SA{|I5hiIgx3@b~L zn3tJH=wK2{tsg0m#tZhc7oXF&cLdG8srFM-l+V$SB=G<0);?Umy@< zYR*VrGy?vr&~k(()(N8(tlLe9mIbYuqKlYNd+Cj4>Ls z2cQSgFM+G1@B+EQpnt}1ZI9cfqA{j)2S@>E0*%cOgeMI~?;*J;f$~@klPJ8XC(SBE z8qJ9VisKSu(L`-#D!5JQCW$gKe0(et~ zBgJPfh&?3{l7N5*07ox#e5d}Tmr}UGsZsrq^hn|%hE8g*q+aEv8IUb-T2h|z{{lxR zc;DhfbIv0qq|(pBj078Fz~r(%K~(F=olqI+j;WNaI7H;5<}^(-(U>?PL+W}E2LwFR zl)Lhlt|1l~tVo0)elC+fOZ&~gKz}@-9zds z!oCAM8tkVw0&F?SYem5=K7ykbgy>TBn9xUaoYMc!5Bq5($(Je~0{ENzF!<5X&}I*Si4kqZ&^WwUi~ z6{3|T80W&g01=p!xwM$5!wzIHpMzFH%T8q-`5eHINg6cZ6#v)l{$Nu(`iR!!XzmMW z!U7ED%0x9zI)oKd_-sy+CUDUwLSeCl5+cJc5EYsf%VchYRYG%`2py?vG|FC+Kw;%y zgQAr1kPTZ?!-FQpSU(Q(1@b1&LxWX&F3Sj+N#-P3z~VTAz1FDhBy>UlGBFbXy(6o-b7c@Fbtyr z4bYK*7x)-7 z|E>SKW<2(AwSdb}X+Y!R<}d_eCZ-Kg?NTBD;RC_Nv`P`N3AilDZ#24z5@T5;6&%n4 zSiZy_E%ffF6SpjyX-iL0OA8EfzOlZ6G&?LZxgAR3vLer{4M6==R1s8;k@{GP@;|;L zfn@*~d6hZfFWlOuj3hokpZXMRcOJSO9#jModaopP1NT8>idCb;*Gac$Q*vD5!|OQYQ*R8}v9F6diFWB1-}N1Xl>{OoT0?+BBL7cn6wcgIESW%@kUI zAIyZDI66K)Wp2Uk)N*p&e+F5CZk*~>sI*oBKZ5gDt5Hwg^#h|#1$DpOaC@|dp z)@Y#9>?>1(ARj_#^D%Tr8({ijLxflR z&%5Qf!6_V;?bu@tyzrzzHa~{Fm*8J)t06$X0-ZDrxKg~497U2g?U=Db#9(R^(nqBj zSs;xc5C(t)-36-!awQ;3ftbJv*?=%3C`5GT8=(T?$fOUDwuD1tx|*~of(&qI>MX!M z$n+TTytQ{ZTwh|@-hJ+Bc-9##qervt{>0+u$9WLowZgrz3JL&*fBr}{?c%eH$eB!C zW+NS&=FjFz$3O6@KS&?juxp-qCZ>d@7O4W*zTUg`v*M`5%Zq$^QKt*VDi*8Il*6l1 zCc1)RpJCMaS2Cbq(J|Zn2AJapo_t~B#>0Us3;yD94_$nnXBMxxxF2)=Sggg%y|4s+ z5at>zrySwa3m`{p2nWhHh0dQr^h{WT+NN7YtBN7^s(1?UaBpisuwWIh&op&96g(+o9_6>f8DV6;MQ}Faq`URdvsrXRY z_+-s78*latk}OPAE? z!I@b(slijS$9I{WK4V(yl(h6|UBnlru`lG{3-Xod?g{p8FsnUlw*XQxk2O)pK&ot8df@|2v&i4&*HNKc#D zQho$q!Zqe(PEQ{rPw4z%ovzCfak+iz7p!i4xtVD=eP+%qh{= z|8SPAEHyWK;><47(kG5# zhgr)0i7q!y@k$*p#$9X5ZnW#L$h-vCQq<5{2oG4t#Fk1p5q zg9rSxgVWD)(ZPb#l5>*9Ve^&@ERI_Bh3CSd$!ntHT5g<_?GfYZ+9Yz{y3OaF4f|+p z25xN(dBJ3MZ8N)BdIqOXnU?2 z@q17%rYR@q4t^wROK5K4iF!Zxn>?fMZ|etG4DPO-)3Ypd7lwFP>bbj)aC2|sGGdtg zM_o^s=*W<}ERE=$I-d20O_`KFBLjvH>1b(BuNggCM~B8o`VLQ@mg5^cIVU}PQhHh@ z{M|QU%9I>me7U)$8GX>hvr+#c3BJRv!-mJ+J7CO^dk2jQ@wRx;TdtlB9ljYIX=&Z8 zerTwrdx#|@Jgj?INK&)u84M(4X)(Ij?d+~M80xsXSsKu%8q(`-t}gC7 z*j2Y`;O6CK`XP8*;|-H7Z*P3T_m#(M|PHuUkfZdk}X(|v>LR9$^8etYw@ z-(P(;=Ys3xUC(cj*BGgrKH%l!ZNI%*FD&ZRg3I0R{yyQJo?lIRY|`J3FUTGH^sGPP zn&fr-7*B(B`#!G4dzEV*_;!5v9=#)158qh)YF?v$eLr>|l>G{&ukwOn9=5=sz9Ip(w%1fo91a@@unp1WqsIBacd_o7HbY-Ie*Way%MssgdLh-4Y-y-Ck->r= zfxG7DFZu$NzueFr)c*3|$&oIvU0U33(4(Gf{;{g~gNHWUyU^}0O^+?RXUf#i&koyp z{NbwT#_uOgJN@aJ!eQsS9jR)0?~JKGzOZe|djmdw+jXV?#DUWvs_c0GyYcS~UcdQ; zem`7W6!5qAbR|D0My%<6Uz6Vhrmr9PM|jf@Uwhy3LquB0r0iF>Ebij-`1ZByKK`uP z6Z2;sF5lkpWZ}RyPoMgB-io!~4qW)?vvpD@)PDQsZ*|kMlG8#)1*|39*ZycLpsj$m z0@?~_E1<1_wgTD;Xe*$tfVKkK3TP{!t$?-y+6rhZpsj$m0@?~_E1<1_wgTD;Xe*$t zfVKkK3TP{!t$?-y+6rhZpsj$m0@?~_E1<1_wgTD;{4cKn4QqxDupe}znCmy+Xs+Lt z&D$#@@bs-YG{C=hLi$YXWSEvYW%9JSWtb^X&dxE7o0vX%d`?F5Pg2q*Cr?VB%qI4I zCnZnL9EVS&CQlqcZdyiiXt(ZSl78;E!HrJTi8!)6=v?2*9|klW_v+p5o~{p%e0ATEwRU9J8k#y$y=8)5YwE=0%t?H^hs&>Ysp}?WPR8|N2SjYY znSnDWW%bI;OzUOoo)(^N>6#W4nwF9h6qeRABq*i3rDsqowl<`93r$N)>zb0ojx|PX zkC>88dnu;8$M22S#g_*BeRFVcl(5X}5z%6@WAa&AsWFnxMV z_B8SKHTzb8nS)i^54zCDy5we0?nQs;mE1GEXL?ExOHlXFu3lWvc(=2)gFp@H?fBTXA92B!2XKK$(a*_*Wu3Y=>U^gdM3GG zX7Gfp@v89B=`4{VPrcS#)P!y*@gjpI>q+nBu!BD2}z&526VHd%C{9mxxMUd6* zWV`8x^Pi1rIUslFpg%{h>D(G-yj#4ap7)Zq;a3WOGIR)O_fhSSZyOfuJHCBO?>Upt9r611=c2(a zFZ;~T+Oc!hZr!DayJdzxo#X!Im~*d3ch;Tz?tf(?OS7JzeA=bQ;suMsJFbozG_2A! zwA((1jVul3EXp4DUfJGnb<2O;Iyvv_Z4J6Sq;?6{0Xj=R7vG*w{`^|E6I&AdcJ;T8 zpZIq8W4;T1ZnEX#+wm8NTukZH-NnT>tfA$lT|tI=cOBUY%gvodO@3(It5L+86b*qSBbrI9Xr*N}#m`tJ3{7R2b&UEN&t z2AyT@Tz3!g$$E|SZY9}nS(eEzdOiPC!S566=q({mUvl$J^M142iE&l+$G!Qh@!7af zyVVZ+d_q}{@w>O%x9|4!D{d!U8g*TJ@2H@*DNY3bgz zM_AV+m%39-b6zQX;kivEwkEwSHutj@o7-U?J&lZcKY9;bblzY)uxt9d6*(Dn0pcq4 z9=PgE$6aB~EKS*_uOt|)1YCNuAyDK_3YL& z$&z}LmvXnIC%=?XXqBrx{)_b#Jgg{GFKZh78RdxTaN@ zanzg_dmYMZKcn;1uuuB*S#hZEA6cW)8+ZP?&F;1(=clBb_q~==c=z!7Lk~P~bY0eo zg|1oa>s(nL+hnI=_$TJvH#?PVUfXf;_MK$(l; zJ#u`vm?k|`+Ucv-Ve_Bb9I`Lv)WQx2gVyv}@=N(ofm@2YM*GdY_WRC~R~DaH;j?T- ziS4ll$71{4TlMhU`}@sqT=u9-V$lx^KFtjtlK7j~?9(G(do9!0wB4ySai4y%;-oIPSAUze z&c#)))4yQTufVR#Vy6wFv|W{N{QTzbt3QRD{r&gPFBDHrZ5|)}N%F$)9P;IlQ-;O; z;~V!mv-f?yWaQ6X4#j@|;k}L<1MB4d$%fOqmbokB?o++qZSDfg+*={1H#k+ETV|V6h$wBH#jvr z$1;ZQzl~d>Wu#?zNkU0{!M*Yv*%Q0||DRx@#g|Ut>xS*gmb&ilX#SI>eiwf3(67h&A+J_0dw;ms3(3D8_nZ4}gYVvGJFn`U+3|1uns0k4x3A^B z^rlx{yl^eM!?=WpljrCg9?B`bciTNjLVCW``^Rw~@AX{}R5Lce;nAQyQRjCbivQu4 z^iKEBPx3GSZuHQ;?HlgWdACW={%qKwuLpfm{H3{(+ccA*WY(l(pG<}F zs6*zgooq-B+IN`|bN*EO*)VhugOiOY5)i z^=td9p<{Co&iFRx{3EgPzrFvJ?aTH@i+Ww*2>v8I?YEOJ7M;Ev_0Ft`1xNngZJNz5=KBe+e>5Hm zuM!FGIg#+{@7Z{t;hNtw!4c8^p1ooV)M!r$FU2v-GzBrIUCvEBWlT`MQ?{NrX*6TT z3siQ}Y6cpN*c-_{na~LMKMO^h?WEdrhk?{bR#B z4+gjB-FE$vFYmhKf5xNi*{6=JJKNrtv?ZQuAdM-cu5B=-0dAsyq zw3;_Qy2aDac&~f+#MtWi2RaP+?QGDdd-7iRKKZKK+ZAq;Bewq*Ieq8j^@4^kYcwTP z-^v!5;`y0>&)ze89m&tyRlebF>#=4vpUkO=8@Mg$qYvhUJve3E>#N3(UD2b(gtsPs zTKCZ6-BXt8(>D2U-x~GAv%mGONnAfQpxX%B(dE4jub*maYTCF%gV#c0jy`ntc)h*W z3)2q_Pxzw6z`Vz27WDq`V2g~gnY|D1^XmBZ>U%D9$!O)ZWXR9!=foa7b=R54n(toQ z?d$kcOM|PjZMIL=ZF+V|w@c&VTgNSLe>tr|$Tw5>4Lp6?{imm@Yqms0x=-Em#o_mK zI~Uel+O%7T*S>Fhb>wI6>)(DPbo}C1YM-bBLBT>_qTa-$-V_4Ef2Z%p8d&vn}2(2V%Vv% z?e_WaIDM$)xCi1MZ#itj{-sO5j(%}>|A3mEfwy)nXsi|rE_n)7z9h~|=+Mq9k$M-$;*2+yj4Tm^pJ#2@kcx%<(;Ta2c zEi+{1LdJW@l6jN)E>eEW;!@B}=AxU(cNC`s!dOm=<~gmmrI#hVqt%tJ%lDdFA>1QKwJi=~FG+-}U%lYuJmAeN;2QdO-`zV?%a6JKBBPx$l0+AC#I| z`oM}4o(JO}`ru+s*9nieyZri@-YsLEJUQ^AV~_5gksSVqU)}g7Zyfr=dg95>v9?(k z3w}L(y2Vq?|9&RW5On&d?4O6Z-V^rWt~Z)JcI=ncsg0`M^W6L1LyZ$`kI#92U5_;< zBQG|4e}|iQ=K}*K6mENX^1{}a5`WgsbNwlC%cg%cKjz!_{R7*!waGqIq&uLWliTg| z_Pw93disl~R=+HJ?&G?@Ebacv@HUHbbcfSkSoBA|qnl22=~mGFh5N3A7Gxv?Thhg{y^B^!k>e;pZ)OIg2L|W zUg8HO~+skW4dZR-xHu*8O`NDg>E8lB4B|L1}{UssKJ(K?B`#ITP_y3_*pWJIw zNQc_#jh8f7_OxxOsUq#fjE$KCUTP6}ZD;#8dc0O<3q03;{HEgl3;Mj;Wl@J`SO4+H zJ!hM4Of(l39cU0(@nuM>XFhoTkKK(bF8dwdeRS5KpErHh_mU;=AC{{*L3^9bKC<+U zf${IWUH_Aumh0YePkQl#1>f3MzdQ2fyDndC)bO4BeF#qH^Mtfowq!Zx6=QA^Hw{HKV>@CCM}h` z6*+Aho>7zL8{1Dl8b&;<1KG-+x-%x8w)2@Mn4>|WD!?W6BLzu`WwQ6sW9XDrLA+VWQJGZUtTE+}#s@L$$K7||^TQJtdv<<($U4i>kw5Hv_<<9z>>cvpx7|*Ke0XfxgMVB6WZtoD z#VaG0%-T6~&$fzPTPJvjyEa;M^ z&^w!2y5>GGbKKEhA^lc$jd-u_=Si{URVO!;jQ@Li$9GDH^xWDwvDxKzqx(OZcKQ9x z1=Ckf?=$LwsOj^ccsg@-%(<`1?rJ%1RnERI2DU!cAa&EpLq9HE+G|apzv;fQwtr&V zs(#HoKe}Ui<-z1hXSyD({&Jw3ZvP`bD|SA4*>B61#;?Eq*iVNSW(7u$eW@bVyWffy z(^DS(Iq%iqzDs+t>wzcg)NX(MWOTXr8!yh#*EU@4>DOw@@PPJDO#HS%!q5RZ?{?U@ zEi(6u*&D|!>bv)yPa_-Ob^o`G;?IOs@5}b`ulMDgLAiZXzxNxn%XjEQQz~9AC>m1r znRU!+->apqt9LwER`IB@!!wt@$?USSRnVoQ8@9Ciwx!Hj&^q+puYdlEy-d4dK2j$v z>zT667ngo$3ke!pv~Hc>>I;)TXn0-1nkBOga?>QsLpPb5qUE=cn|jEc^3QUU2*%ic zlvtu8S$4AW>~xO>`Kbi?sUS=q=})uM^@o{gX-tRlbu(I!)!p6jSDh2OxZ3o3T~^6^ z(W6J43LjJQLQcy*+q^!%{N8qOkEQW_u1>F)@%Zyo_8s?K)NQPfi|dEJ zuWn2%X%LZGz0&{vEu$`tc<7UkFHfBQ@szef7ayHEq3uKd*Y0{_cu>bjkMufvZrl8) ze!u^~=#Abx2R}dUb^V6t?|bP%^Ve@K53#w@a}QWr zD6(BW*O2-SN~6Wc{+=PEAs*AERtGJeRUdZo4Dqqx!A=%Dyz1{MmW>6*Aj?C?!YeXn zC0OeG7Wt^9i~Ue;A^vW@-x!vjnbPCQ)~){@KW*vS>U+n$F|*Ct>Bl;n7vv6UF!RB` zS$e3C-X+9r>0m*kar2EU>01`~`RK>1M=yKQws`zQ&u!ZGTHh0sI;?sj?0o;m#;$4y zgb3$mbJ4j3x*ThMBx~F&ldFDdQTn&(u7>m>=bAlH??96dfe(L_*z@Q6d-N?Wyc8aO z_@&kQm%obGPeW}CSPCR^d*Tt>gYbRIF zdAQl)habBC+_~J%6P_#nn_Io=J*{7UHa~vLy;ncjuy^&h@8pecWtsm<&^>;mvg7<; zOP;5XvF>s|Gv=$K3#NzHeYt8>`EOBaB|EZx*DP!lcYJfVX-|K5v~&aX4cTrsfK&@fA}q3t(Atj#C(`t|K+R&}m@GCNiE4K{(WR**5^CEz?_tkG&eyZ^yz`wN3jR|EBl1br=1;ZTY$ehX=l~ zBh5rJ0Hd08R!;$Ff7ez(TLEnav=z`+KwANA1+*2=RzO<;Z3VOy&{jZO0c{1e70_1T z4qgE~&pp2I?%rRY{pyFzXJ2a6KYB`U@B9AP9HxI|RLG*U#?kFx3I6+@2i?EyWH@~x zD|`JDKf3#ET$2Ce?`r~dSI@M0F!04uy{_%hHLhzIQCH`xZ>)1^taoj!zjj;~t;76P z9d|?BI(6#Qt5?_PX{zt(;o;f*t|kplty;8c-KvF;k8iu+j=uhZZGC*qJpuwPAz@)* zZ94Ys6CT<-xNBG_9YkNRUOi6_Pw)Emy+i$c{6hcjKi78Y+URt8gP#47hSs~dx*6Q- z)U9Xqzz4QA(qSCe)y2)#U~qH8rys%lI=9A#yZk~T-J1+euH!$|6gs!Kv~Js|iuao) z95~mmYf84Qp3$qBck>qQJ9IP$bPDU%y+?S@UeW!m{RhOv#tj=jV&r{^qmok7(#MU@ z$eb`OCwKacnX_ikdpy5j{(>h8mo8hr;;E-sKC`Oqh4Qs8u3Nw1r7f>jZr%3!8*jc< zwR2bX?mZvu-FNWQLx(^6{K(N`wO^n3=G&8}zWc}N^FLjzbm9ODo>5*S2fIxuz-E z2aI0r!fM-}r(0upc5^po`ybtza%1-UI;Qi){G>}`*T%ZOx(ln$4O!(iwXMW~cT+)-!6RZv0$xjU5Aw@*#m8cj@xhP`yc9q$8jJy+W_mwbAA#>2 z^e&s5?}uZRjAd`TdM&BKcT#HN<3lp(^RC8dIu|=Z4PL@`3xZtrkx_KwBzG4&aZQNz zx^tO=aB_BJIypa>31^iDE6wmOGSKYH0bbzzJ?|L^G`zMZ0?0 z;SUT)qOsmQGWqh3yqXuS?*{PGPR9C!Fh;16^XM}Hs(;IE5w@z58r5nn05 zMjBi=m2{p+;SV>28Ke8Z^}CnFQShJZDvjXjc z8K~ornTv%wqFyix_ye89L?#CMb7)tRy;+)zp@ubMRyikU91tJk=ECV1F~Fx5J80H=WxwatqQd5Mvz=jv8YH8 zj+Ru;Rl+SM_}bh&?ss-xDHFtv{E;$^6$wK6h@gTpK@^%P_W-I`W?b>=(y zKd$@FAiAyPltUTUl2OPgwZd~)635a?+etF#7=I>_j~So8o@<#B%p7^0NS-hUti|J1 zA%7|);Qk@f2)i~>JE&>|c#hf8BbD^R2xWq|M%F;qF4d_mqu?DHDY7X?abDkHX%X`x zsO|6!5D<*j^ti@ThA0o?vkO|@ihv7_2z_Sc$QNT%REx4avunA9ll8GmlArgXg zIV5!s`8?Pay2FtY&T%Nb(zx6P&GE{Sji4=_6u zk3_QklgO6ky@eA&rMfb~2CQ&XGA<(cMpYEqnK5ph#C}E~IdZnJzW{Y`3JxE*Y`QJr z7Qc%-9my$+_5S$X=dOl}FjH00?m_;DRZ(I|{X|(g7ETB?ZX`Q*R>F+%mV-qmsY3j9 zo`A(MC3Gv?nR%Qv9rG!5cDbHju*5Bn%xQ$e8)c;^r2>}4J@4qOEf@=u_dh<_$vCc4 zm~mpu_z#6NFO!sV2n%cgO=1+ACsq}U6M~+H1bi8OLNJjbT_|4{WxkRvQMsQ1{7GRm@TNYXPM2&=5-~gOPSX2f0?(&#Ksrebz1aPQA{TQ%IOUmS;yp59I z-j9`u9(SSBk~i;(RY@@drlSQe6s%|mr${nrMQ}=HErf4H5JyD8vI>hAZcrluR>ZE9 zYUL@1#(?YD3wAJWf$Rz%pxGjMqb$@^Mr7jqKhGj1!h1B?9O1qCk)BtwoE33&Hed!6@EkQ{DHwf+x5K!wcA?j=^Ul>u zts7Hmlt*uBDvj*tB*kHw?LZ)twPT4bdYaNWG#kUiQdC}rRvyd>PB22C$`(M9>xN#G zfh&BMz+Y4bLe>}O3edQkiMPb64k`+V9Y|p~6~OY0int<*&GoTq0# z0!<-~5&^tjqP$a&l!^oPe!gR&iGwiCU=CoV04qCgL`s>%It_R$LIYhtoj^+!lq&eB zD<^djaKw5`sT?6IsyHGH=!Vk^Cf>*r6}pnd#kwjL8ezH}FBs?$P&n{MnQR2v6AIFG zNl5WTPF<8blb=A6eM&-XM2~xoKq6jdXx7#VYgbXOXQ|cdM4FXJQhSFQ;Op3gfq#fd z1~DMf-Bpo!TpTgnd6-?;%E7bH7~n}>o>4SmxUz)IgBM*30=AyfB%3P`jutxh) z=UoVaBJX1t%z=vbx(Jq|dS3(y)8rzfB-w{4`NDJBh9Z!uupD;%5a_h}zyszUYSGOA z(M1ZnN}d20z`1xnz%>X=zcCU-MzS->{;H&ixNd$aCI;<_cxXogO~v62w+50gbCb#K zLy%Y1RdtZ7$em0;BWaE-rwHxt>S7CFO&HG>zKRib4j?eTn&mlmWpS_@c4aeAfGDY- z9ZZ(}BKN2r61hh;kf&}YX^Olqp0(5GdpPG~EIgnV&@7uG0%Ca-%7`}$snjV76`9rF z#vnjcVoI0^)5&=l&;UfR$x%4Ql{NP{)VM9V>h zWdg?5d3I4`V1zP17f;^um^5icCztluJLGtoq3YPDz zu2cV==MAO;Ip+ms1FRDh5G_8?22z|nS%;NeL9smd2+%njlwZj@;IQmmJjvMAAnwSL zx;U{88o?*f;k_N4k!3M^pF-mWc3!}QQ(1Y-ZDR`cW(#RRO+dIHZ6b5LRO3a1rNE9C zj0rmC8bE{{FM#gK=&Q^KB^(z728jU-yIAN{Sa82kC?HO)QlO<+$Q1)EQ>ZhA7&v8B z6D^q|n-?5g?vt?P@wXl7_Zm+1sPAiZNcBv~x7X?99;(?1#eLK{2p1sgw?F9`q95 z04De%w<-z`tYja>l$pViIarNKr7i_tPBS?};F$oWq!b5uHn)>O=01u{sD!?({@F2= zfl|8=!Ctq+25QkHrDH`tg}abJK*z*g7__Q_?XM}p{u-5%�hN`_AbhCzAb39SABM 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 literal 0 HcmV?d00001 diff --git a/sdk/examples/cawg_identity.rs b/sdk/examples/cawg_identity.rs index 4c371d00a..c7f9663b0 100644 --- a/sdk/examples/cawg_identity.rs +++ b/sdk/examples/cawg_identity.rs @@ -124,7 +124,8 @@ mod cawg { builder.sign_file_async(&signer, source, &dest).await?; - let reader = Reader::from_file_with_cawg_async(dest).await?; + //let reader = Reader::from_file_async(dest).await?; + let reader = Reader::from_file(dest)?; println!("{reader}"); Ok(()) } diff --git a/sdk/src/identity/validator.rs b/sdk/src/identity/validator.rs index 6022b488d..a20497486 100644 --- a/sdk/src/identity/validator.rs +++ b/sdk/src/identity/validator.rs @@ -78,7 +78,7 @@ mod tests { let mut stream = Cursor::new(CONNECTED_IDENTITIES_VALID); - let reader = Reader::from_stream_with_cawg_async("image/jpeg", &mut stream) + let reader = Reader::from_stream_async("image/jpeg", &mut stream) .await .unwrap(); @@ -104,7 +104,7 @@ mod tests { let mut stream = Cursor::new(MULTIPLE_IDENTITIES_VALID); - let reader = Reader::from_stream_with_cawg_async("image/jpeg", &mut stream) + let reader = Reader::from_stream_async("image/jpeg", &mut stream) .await .unwrap(); @@ -126,7 +126,7 @@ mod tests { async fn test_cawg_validate_with_hard_binding_missing() { let mut stream = Cursor::new(NO_HARD_BINDING); - let reader = Reader::from_stream_with_cawg_async("image/jpeg", &mut stream) + let reader = Reader::from_stream_async("image/jpeg", &mut stream) .await .unwrap(); diff --git a/sdk/src/identity/x509/x509_credential_holder.rs b/sdk/src/identity/x509/x509_credential_holder.rs index 80d1cd33b..b6209c687 100644 --- a/sdk/src/identity/x509/x509_credential_holder.rs +++ b/sdk/src/identity/x509/x509_credential_holder.rs @@ -134,9 +134,7 @@ mod tests { // Read back the Manifest that was generated. dest.rewind().unwrap(); - let manifest_store = Reader::from_stream_with_cawg_async(format, &mut dest) - .await - .unwrap(); + let manifest_store = Reader::from_stream_async(format, &mut dest).await.unwrap(); assert_eq!(manifest_store.validation_status(), None); @@ -144,6 +142,8 @@ mod tests { 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")) 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 5496ae544..c8ec2d24b 100644 --- a/sdk/src/reader.rs +++ b/sdk/src/reader.rs @@ -35,16 +35,15 @@ use crate::{ crypto::base64, dynamic_assertion::PartialClaim, error::{Error, Result}, - identity::validator::CawgValidator, 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, }; @@ -132,27 +131,7 @@ impl Reader { /// ``` /// /// # Note - /// This function does not validate [CAWG identity] assertions that may be - /// contained within any C2PA Manifests. If an async call is feasible, use - /// [from_stream_with_cawg_async()]; if not, you can construct an async runtime - /// on your own and perform the CAWG validation separately as shown in the - /// following example: - /// - /// ```no_run - /// use std::io::Cursor; - /// - /// use c2pa::{identity::validator::CawgValidator, Reader}; - /// let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); - /// let mut reader = Reader::from_stream("image/jpeg", stream).unwrap(); - /// let runtime = tokio::runtime::Runtime::new().unwrap(); - /// runtime - /// .block_on(reader.post_validate_async(&CawgValidator {})) - /// .unwrap(); - /// println!("{}", reader.json()); - /// ``` - /// - /// [CAWG identity]: https://cawg.io/identity - /// [from_stream_with_cawg_async()]: Self::from_stream_with_cawg_async + /// [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 { @@ -165,7 +144,7 @@ impl Reader { Store::from_stream_async(format, &mut stream, verify, &mut validation_log).await }?; - Self::from_store(store, &validation_log) + Self::from_store(store, &mut validation_log) } #[async_generic] @@ -183,66 +162,6 @@ impl Reader { Self::from_store(store, &validation_log) } - /// Create a manifest store [`Reader`] from a stream. A `Reader` is used to - /// validate C2PA data from an asset. This variation also validates - /// [CAWG identity] assertions within the C2PA data. - /// - /// # 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. - /// ```ignore - /// # async fn main() { - /// use std::io::Cursor; - /// - /// use c2pa::{identity::validator::CawgValidator, Reader}; - /// - /// let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); - /// let reader = Reader::from_stream_with_cawg_async("image/jpeg", stream) - /// .await - /// .unwrap(); - /// println!("{}", reader.json()); - /// # } - /// ``` - /// - /// [CAWG identity]: https://cawg.io/identity - /// [from_stream_with_cawg_async()]: Self::from_stream_with_cawg_async - #[cfg(not(target_arch = "wasm32"))] - pub async fn from_stream_with_cawg_async( - format: &str, - stream: impl Read + Seek + Send, - ) -> Result { - let mut reader = Self::from_stream_async(format, stream).await?; - - if get_settings_value::("verify.verify_after_reading")? { - reader.post_validate_async(&CawgValidator {}).await?; - } - - Ok(reader) - } - - #[cfg(target_arch = "wasm32")] - pub async fn from_stream_with_cawg_async( - format: &str, - stream: impl Read + Seek, - ) -> Result { - let mut reader = Self::from_stream_async(format, stream).await?; - - if get_settings_value::("verify.verify_after_reading")? { - reader.post_validate_async(&CawgValidator {}).await?; - } - - Ok(reader) - } - #[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. @@ -266,25 +185,7 @@ impl Reader { /// ``` /// /// # Note - /// This function does not validate [CAWG identity] assertions that may be - /// contained within any C2PA Manifests. If an async call is feasible, use - /// [from_file_with_cawg_async()]; if not, you can construct an async runtime - /// on your own and perform the CAWG validation separately as shown in the - /// following example: - /// - /// ```ignore - /// use c2pa::{identity::validator::CawgValidator, Reader}; - /// - /// let mut reader = Reader::from_file("path/to/file.jpg").unwrap(); - /// let runtime = tokio::runtime::Runtime::new().unwrap(); - /// runtime - /// .block_on(reader.post_validate_async(&CawgValidator {})) - /// .unwrap(); - /// println!("{}", reader.json()); - /// ``` - /// - /// [CAWG identity]: https://cawg.io/identity - /// [from_file_with_cawg_async()]: Self::from_file_with_cawg_async + /// [CAWG identity] assertions require async calls for validation. #[async_generic] pub fn from_file>(path: P) -> Result { let path = path.as_ref(); @@ -319,58 +220,6 @@ impl Reader { } } - #[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 - /// - /// ```no_run - /// use c2pa::Reader; - /// let reader = Reader::from_file("path/to/file.jpg").unwrap(); - /// ``` - /// - /// # Note - /// This function does not validate [CAWG identity] assertions that may be - /// contained within any C2PA Manifests. If an async call is feasible, use - /// [from_file_with_cawg_async()]; if not, you can construct an async runtime - /// on your own and perform the CAWG validation separately as shown in the - /// following example: - /// - /// ```ignore - /// use c2pa::{identity::validator::CawgValidator, Reader}; - /// - /// let mut reader = Reader::from_file("path/to/file.jpg").unwrap(); - /// let runtime = tokio::runtime::Runtime::new().unwrap(); - /// runtime - /// .block_on(reader.post_validate_async(&CawgValidator {})) - /// .unwrap(); - /// println!("{}", reader.json()); - /// ``` - /// - /// [CAWG identity]: https://cawg.io/identity - /// [from_file_with_cawg_async()]: Self::from_file_with_cawg_async - pub async fn from_file_with_cawg_async>(path: P) -> Result { - let mut reader = Self::from_file_async(path).await?; - - if get_settings_value::("verify.verify_after_reading")? { - reader.post_validate_async(&CawgValidator {}).await?; - } - - Ok(reader) - } - /// Create a manifest store [`Reader`] from a JSON string. /// # Arguments /// * `json` - A JSON string containing a manifest store definition. @@ -422,7 +271,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. @@ -461,7 +310,7 @@ impl Reader { .await }?; - Self::from_store(store, &validation_log) + Self::from_store(store, &mut validation_log) } #[cfg(feature = "file_io")] @@ -488,7 +337,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), } } @@ -844,9 +693,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(); @@ -854,20 +701,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 @@ -882,15 +738,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); From aeab1bca62962612688977c42edf40596f0cfe2c Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Wed, 1 Oct 2025 13:07:36 -0700 Subject: [PATCH 09/10] Reader::from_stream_async should use Store::from_store_async --- sdk/src/reader.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sdk/src/reader.rs b/sdk/src/reader.rs index c8ec2d24b..1b18512b8 100644 --- a/sdk/src/reader.rs +++ b/sdk/src/reader.rs @@ -144,7 +144,11 @@ impl Reader { Store::from_stream_async(format, &mut stream, verify, &mut validation_log).await }?; - Self::from_store(store, &mut validation_log) + if _sync { + Self::from_store(store, &mut validation_log) + } else { + Self::from_store_async(store, &mut validation_log).await + } } #[async_generic] @@ -159,7 +163,11 @@ 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")] From a612b45a788fe11f9dce079763d06d5b183c104e Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Fri, 3 Oct 2025 09:57:18 -0700 Subject: [PATCH 10/10] Plan to resolve compatibility issues for `IdentityAssertion::from_manifest` --- sdk/src/identity/identity_assertion/assertion.rs | 4 ++++ sdk/src/identity/x509/async_x509_credential_holder.rs | 3 +++ sdk/src/identity/x509/x509_credential_holder.rs | 3 +++ 3 files changed, 10 insertions(+) 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/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 b6209c687..43b6e2a5f 100644 --- a/sdk/src/identity/x509/x509_credential_holder.rs +++ b/sdk/src/identity/x509/x509_credential_holder.rs @@ -134,6 +134,9 @@ mod tests { // Read back the Manifest that was generated. dest.rewind().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);