From 85201a7fcb7104adb86a81e700bde68e2f4dddcb Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Fri, 7 Feb 2025 10:17:56 -0800 Subject: [PATCH] feat: Move cawg parsing for c2patool into own file --- cli/src/cawg_parser.rs | 370 +++++++++++++++++++++++++++++++++++++++++ cli/src/main.rs | 358 +-------------------------------------- 2 files changed, 373 insertions(+), 355 deletions(-) create mode 100644 cli/src/cawg_parser.rs diff --git a/cli/src/cawg_parser.rs b/cli/src/cawg_parser.rs new file mode 100644 index 000000000..23dab8233 --- /dev/null +++ b/cli/src/cawg_parser.rs @@ -0,0 +1,370 @@ +// Copyright 2022 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 c2pa::{Error, Reader}; +use cawg_identity::{claim_aggregation::IcaSignatureVerifier, IdentityAssertion}; +use serde_json::{Map, Value}; +use tokio::runtime::Runtime; + +/// Update/decorate the displayed JSON assertions for a more human-readable JSON output. +fn decorate_cawg_assertion_from_detailed_report( + reader: Reader, + json_content: &mut Value, + tokio_runtime: &Runtime, +) -> Result<(), Error> { + if let Value::Object(map) = json_content { + // Iterate over the key-value pairs + for (key, value) in &mut *map { + // Get additional CAWG details + + // Get the assertions as array from the JSON + let assertions = match value.get_mut("assertion_store") { + Some(assertions) => assertions, + None => { + return Err(crate::Error::JsonSerializationError( + "Could not parse JSON assertion store as object".to_string(), + )); + } + }; + + let cawg_assertion = match assertions.get_mut("cawg.identity") { + Some(cawg_assertion) => cawg_assertion, + None => { + return Err(crate::Error::JsonSerializationError( + "Could not parse CAWG identity details from assertion store".to_string(), + )); + } + }; + + let holding_manifest = match reader.get_manifest(key) { + Some(holding_manifest) => holding_manifest, + None => { + return Err(crate::Error::JsonSerializationError( + "Could not recover manifest holding CAWG data".to_string(), + )); + } + }; + + let parsed_cawg_json_string = + match get_cawg_details_for_manifest(holding_manifest, tokio_runtime) { + Some(parsed_cawg_json_string) => parsed_cawg_json_string, + None => { + // Not a show-stopper: + // Could not parse CAWG details for manifest, + // so leaving original raw data unformatted. + return Ok(()); + } + }; + + cawg_assertion["signature"] = match serde_json::from_str(&parsed_cawg_json_string) { + Ok(decoded_cawg_assertion) => decoded_cawg_assertion, + Err(err) => { + return Err(crate::Error::JsonSerializationError(err.to_string())); + } + }; + + let cawg_assertion = match cawg_assertion.as_object_mut() { + Some(cawg_assertion) => cawg_assertion, + None => { + return Err(crate::Error::JsonSerializationError( + "Could not parse CAWG assertion data as object to decorate for display" + .to_string(), + )); + } + }; + cawg_assertion.remove("pad1"); + cawg_assertion.remove("pad2"); + } + } + + Ok(()) +} + +/// Update/decorate the displayed JSON assertions for a more human-readable JSON output. +fn decorate_json_assertions( + reader: Reader, + json_content: &mut Value, + tokio_runtime: &Runtime, +) -> Result<(), Error> { + if let Value::Object(map) = json_content { + // Iterate over the key-value pairs + for (key, value) in &mut *map { + // Get additional CAWG details + let current_manifest = reader.get_manifest(key); + let current_manifest = match current_manifest { + Some(current_manifest) => current_manifest, + None => { + return Err(crate::Error::JsonSerializationError( + "Could not get current manifest".to_string(), + )); + } + }; + + // Get the assertions as array from the JSON + let assertions = match value.get_mut("assertions") { + Some(assertions) => assertions, + None => { + return Err(crate::Error::JsonSerializationError( + "Could not parse JSON assertions as object".to_string(), + )); + } + }; + let assertions_array = match assertions.as_array_mut() { + Some(assertions_array) => assertions_array, + None => { + return Err(crate::Error::JsonSerializationError( + "Could not parse JSON assertions as array".to_string(), + )); + } + }; + + // Loop over the assertions to process those of interest + for assertion in assertions_array { + let label = match assertion.get("label") { + Some(label) => label.to_string(), + None => { + return Err(crate::Error::JsonSerializationError( + "Could not parse assertion label".to_string(), + )); + } + }; + + // for CAWG assertions, further parse the signature + if label.contains("cawg.identity") { + decorate_json_cawg_assertions(current_manifest, assertion, tokio_runtime)?; + } + } + } + } + + Ok(()) +} + +/// Update/decorate the displayed CAWG assertion for a more human-readable JSON output. +fn decorate_json_cawg_assertions( + holding_manifest: &c2pa::Manifest, + assertion: &mut Value, + tokio_runtime: &Runtime, +) -> Result<(), Error> { + let parsed_cawg_json_string = + match get_cawg_details_for_manifest(holding_manifest, tokio_runtime) { + Some(parsed_cawg_json_string) => parsed_cawg_json_string, + None => { + // Could not parse CAWG details for manifest (leaving original raw data unformatted). + // Not a fatal failure, so leaving raw data unformatted. + return Ok(()); + } + }; + + // Let's look at the assertion data + let assertion_data = match assertion.get_mut("data") { + Some(assertion_data) => assertion_data, + None => { + return Err(crate::Error::JsonSerializationError( + "Could not parse CAWG assertion data".to_string(), + )); + } + }; + + // Update signature with parsed content + let parsed_signature = match serde_json::from_str(&parsed_cawg_json_string) { + Ok(parsed_signature) => parsed_signature, + Err(err) => { + return Err(crate::Error::JsonSerializationError(err.to_string())); + } + }; + assertion_data["signature"] = parsed_signature; + + // We don't need to show the padding fields either + let assertion_data_map = match assertion_data.as_object_mut() { + Some(assertion_data_map) => assertion_data_map, + None => { + return Err(crate::Error::JsonSerializationError( + "Could not parse CAWG assertion data as object".to_string(), + )); + } + }; + assertion_data_map.remove("pad1"); + assertion_data_map.remove("pad2"); + + Ok(()) +} + +/// Parse additional CAWG details from the manifest store to update displayed results. +/// As CAWG mostly async, this will block on network requests for checks using a tokio runtime. +fn get_cawg_details_for_manifest( + manifest: &c2pa::Manifest, + tokio_runtime: &Runtime, +) -> Option { + let ia_iter = IdentityAssertion::from_manifest(manifest); + + // TODO: Determine what should happen when multiple identities are reported (currently only 1 is supported) + let mut parsed_cawg_json = String::new(); + + ia_iter.for_each(|ia| { + let identity_assertion = match ia { + Ok(ia) => ia, + Err(err) => { + eprintln!("Could not parse CAWG identity assertion: {:?}", err); + return; + } + }; + + let isv = IcaSignatureVerifier {}; + let ica_validated = tokio_runtime.block_on(identity_assertion.validate(manifest, &isv)); + let ica = match ica_validated { + Ok(ica) => ica, + Err(err) => { + eprintln!("Could not validate CAWG identity assertion: {:?}", err); + return; + } + }; + + parsed_cawg_json = match serde_json::to_string(&ica) { + Ok(parsed_cawg_json) => parsed_cawg_json, + Err(err) => { + eprintln!( + "Could not parse CAWG identity claims aggregation details for manifest: {:?}", + err + ); + return; + } + }; + }); + + if parsed_cawg_json.is_empty() { + return None; + } + + // Get the JSON as mutable, so we can further parse and format CAWG data + let maybe_map = serde_json::from_str(parsed_cawg_json.as_str()); + let mut map: Map = match maybe_map { + Ok(map) => map, + Err(err) => { + eprintln!( + "Could not parse convert CAWG identity claims details to JSON string map: {:?}", + err + ); + return None; + } + }; + + // Get the credentials subject information... + let credentials_subject_maybe = map.get_mut("credentialSubject"); + let credentials_subject = match credentials_subject_maybe { + Some(credentials_subject) => credentials_subject, + None => { + eprintln!("Could not find credential subject in CAWG details for manifest"); + return None; + } + }; + let credentials_subject_as_obj = credentials_subject.as_object_mut(); + let credential_subject_details = match credentials_subject_as_obj { + Some(credentials_subject) => credentials_subject, + None => { + eprintln!("Could not parse credential subject as object in CAWG details for manifest"); + return None; + } + }; + // As per design CAWG has some repetition between assertion an signature (c2paAsset field) + // so we remove the c2paAsset field from the credential subject details too + credential_subject_details.remove("c2paAsset"); + + // return the for-display json-formatted string + let serialized_content = serde_json::to_string(&map); + match serialized_content { + Ok(serialized_content) => Some(serialized_content), + Err(err) => { + eprintln!("Could not parse CAWG details for manifest: {:?}", err); + None + } + } +} + +/// Update/decorate the detailed displayed JSON string for a more human-readable JSON output. +pub(crate) fn decorate_json_detailed_display( + reader: Reader, + extracted_report: String, + tokio_runtime: &Runtime, +) -> String { + let mut report_json_map: Map = match serde_json::from_str(&extracted_report) { + Ok(report_json_map) => report_json_map, + Err(err) => { + eprintln!("Could not parse extracted JSON report: {:?}", err); + return String::new(); + } + }; + + let manifests = match report_json_map.get_mut("manifests") { + Some(manifests) => manifests, + None => { + eprintln!("No parsable JSON in manifest store (key: manifests)"); + return String::new(); + } + }; + + match decorate_cawg_assertion_from_detailed_report(reader, manifests, tokio_runtime) { + Ok(_) => (), + Err(err) => { + eprintln!("Could not decorate detailed JSON for display: {:?}", err); + return String::new(); + } + }; + + match serde_json::to_string_pretty(&report_json_map) { + Ok(decorated_result) => decorated_result, + Err(err) => { + eprintln!( + "Could not decorate displayed detailed JSON with additional details: {:?}", + err + ); + String::new() + } + } +} + +/// Update/decorate the displayed JSON string for a more human-readable JSON output. +pub(crate) fn decorate_json_display(reader: Reader, tokio_runtime: &Runtime) -> String { + let mut reader_content = match reader.json_value_map() { + Ok(mapped_json) => mapped_json, + Err(_) => { + eprintln!("Could not parse manifest store JSON content"); + return String::new(); + } + }; + + let manifests_json_content = match reader_content.get_mut("manifests") { + Some(json) => json, + None => { + eprintln!("No JSON to parse in manifest store (key: manifests)"); + return String::new(); + } + }; + + // Update assertion with more details (eg. for CAWG) + match decorate_json_assertions(reader, manifests_json_content, tokio_runtime) { + Ok(_) => (), + Err(err) => { + eprintln!("Could not decorate JSON assertions for display: {:?}", err); + } + }; + match serde_json::to_string_pretty(&reader_content) { + Ok(decorated_result) => decorated_result, + Err(err) => { + eprintln!( + "Could not decorate displayed JSON with additional details: {:?}", + err + ); + String::new() + } + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs index d07a6cd86..c5ee8ffa6 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -27,17 +27,16 @@ use std::{ use anyhow::{anyhow, bail, Context, Result}; use c2pa::{Builder, ClaimGeneratorInfo, Error, Ingredient, ManifestDefinition, Reader, Signer}; -use cawg_identity::{claim_aggregation::IcaSignatureVerifier, IdentityAssertion}; use clap::{Parser, Subcommand}; use log::debug; use serde::Deserialize; -use serde_json::{Map, Value}; use signer::SignConfig; use tokio::runtime::Runtime; use url::Url; use crate::{ callback_signer::{CallbackSigner, CallbackSignerConfig, ExternalProcessRunner}, + cawg_parser::{decorate_json_detailed_display, decorate_json_display}, info::info, }; @@ -45,6 +44,7 @@ mod info; mod tree; mod callback_signer; +mod cawg_parser; mod signer; /// Tool for displaying and creating C2PA manifests. @@ -427,358 +427,6 @@ fn verify_fragmented(init_pattern: &Path, frag_pattern: &Path) -> Result String { - let mut report_json_map: Map = match serde_json::from_str(&extracted_report) { - Ok(report_json_map) => report_json_map, - Err(err) => { - println!("Could not parse extracted JSON report: {:?}", err); - return String::new(); - } - }; - - let manifests = match report_json_map.get_mut("manifests") { - Some(manifests) => manifests, - None => { - println!("No parsable JSON in manifest store (key: manifests)"); - return String::new(); - } - }; - - match decorate_cawg_assertion_from_detailed_report(reader, manifests, tokio_runtime) { - Ok(_) => (), - Err(err) => { - println!("Could not decorate detailed JSON for display: {:?}", err); - } - }; - - match serde_json::to_string_pretty(&report_json_map) { - Ok(decorated_result) => decorated_result, - Err(err) => { - println!( - "Could not decorate displayed detailed JSON with additional details: {:?}", - err - ); - String::new() - } - } -} - -/// Update/decorate the displayed JSON assertions for a more human-readable JSON output. -fn decorate_cawg_assertion_from_detailed_report( - reader: Reader, - json_content: &mut Value, - tokio_runtime: &Runtime, -) -> Result<(), Error> { - if let Value::Object(map) = json_content { - // Iterate over the key-value pairs - for (key, value) in &mut *map { - // Get additional CAWG details - - // Get the assertions as array from the JSON - let assertions = match value.get_mut("assertion_store") { - Some(assertions) => assertions, - None => { - return Err(crate::Error::JsonSerializationError( - "Could not parse JSON assertion store as object".to_string(), - )); - } - }; - - let cawg_assertion = match assertions.get_mut("cawg.identity") { - Some(cawg_assertion) => cawg_assertion, - None => { - return Err(crate::Error::JsonSerializationError( - "Could not parse CAWG identity details from assertion store".to_string(), - )); - } - }; - - let holding_manifest = match reader.get_manifest(key) { - Some(holding_manifest) => holding_manifest, - None => { - return Err(crate::Error::JsonSerializationError( - "Could not recover manifest holding CAWG data".to_string(), - )); - } - }; - - let parsed_cawg_json_string = - match get_cawg_details_for_manifest(holding_manifest, tokio_runtime) { - Some(parsed_cawg_json_string) => parsed_cawg_json_string, - None => { - // Not a show-stopper: - // Could not parse CAWG details for manifest, - // so leaving original raw data unformatted. - return Ok(()); - } - }; - - cawg_assertion["signature"] = match serde_json::from_str(&parsed_cawg_json_string) { - Ok(decoded_cawg_assertion) => decoded_cawg_assertion, - Err(err) => { - return Err(crate::Error::JsonSerializationError(err.to_string())); - } - }; - - let cawg_assertion = match cawg_assertion.as_object_mut() { - Some(cawg_assertion) => cawg_assertion, - None => { - return Err(crate::Error::JsonSerializationError( - "Could not parse CAWG assertion data as object to decorate for display" - .to_string(), - )); - } - }; - cawg_assertion.remove("pad1"); - cawg_assertion.remove("pad2"); - } - } - - Ok(()) -} - -/// Update/decorate the displayed JSON string for a more human-readable JSON output. -fn decorate_json_display(reader: Reader, tokio_runtime: &Runtime) -> String { - let mut reader_content = match reader.json_value_map() { - Ok(mapped_json) => mapped_json, - Err(_) => { - eprintln!("Could not parse manifest store JSON content"); - return String::new(); - } - }; - - let manifests_json_content = match reader_content.get_mut("manifests") { - Some(json) => json, - None => { - eprintln!("No JSON to parse in manifest store (key: manifests)"); - return String::new(); - } - }; - - // Update assertion with more details (eg. for CAWG) - match decorate_json_assertions(reader, manifests_json_content, tokio_runtime) { - Ok(_) => (), - Err(err) => { - eprintln!("Could not decorate JSON assertions for display: {:?}", err); - } - }; - match serde_json::to_string_pretty(&reader_content) { - Ok(decorated_result) => decorated_result, - Err(err) => { - eprintln!( - "Could not decorate displayed JSON with additional details: {:?}", - err - ); - String::new() - } - } -} - -/// Update/decorate the displayed JSON assertions for a more human-readable JSON output. -fn decorate_json_assertions( - reader: Reader, - json_content: &mut Value, - tokio_runtime: &Runtime, -) -> Result<(), Error> { - if let Value::Object(map) = json_content { - // Iterate over the key-value pairs - for (key, value) in &mut *map { - // Get additional CAWG details - let current_manifest = reader.get_manifest(key); - let current_manifest = match current_manifest { - Some(current_manifest) => current_manifest, - None => { - return Err(crate::Error::JsonSerializationError( - "Could not get current manifest".to_string(), - )); - } - }; - - // Get the assertions as array from the JSON - let assertions = match value.get_mut("assertions") { - Some(assertions) => assertions, - None => { - return Err(crate::Error::JsonSerializationError( - "Could not parse JSON assertions as object".to_string(), - )); - } - }; - let assertions_array = match assertions.as_array_mut() { - Some(assertions_array) => assertions_array, - None => { - return Err(crate::Error::JsonSerializationError( - "Could not parse JSON assertions as array".to_string(), - )); - } - }; - - // Loop over the assertions to process those of interest - for assertion in assertions_array { - let label = match assertion.get("label") { - Some(label) => label.to_string(), - None => { - return Err(crate::Error::JsonSerializationError( - "Could not parse assertion label".to_string(), - )); - } - }; - - // for CAWG assertions, further parse the signature - if label.contains("cawg.identity") { - decorate_json_cawg_assertions(current_manifest, assertion, tokio_runtime)?; - } - } - } - } - - Ok(()) -} - -/// Update/decorate the displayed CAWG assertion for a more human-readable JSON output. -fn decorate_json_cawg_assertions( - holding_manifest: &c2pa::Manifest, - assertion: &mut Value, - tokio_runtime: &Runtime, -) -> Result<(), Error> { - let parsed_cawg_json_string = - match get_cawg_details_for_manifest(holding_manifest, tokio_runtime) { - Some(parsed_cawg_json_string) => parsed_cawg_json_string, - None => { - // Could not parse CAWG details for manifest (leaving original raw data unformatted). - // Not a fatal failure, so leaving raw data unformatted. - return Ok(()); - } - }; - - // Let's look at the assertion data - let assertion_data = match assertion.get_mut("data") { - Some(assertion_data) => assertion_data, - None => { - return Err(crate::Error::JsonSerializationError( - "Could not parse CAWG assertion data".to_string(), - )); - } - }; - - // Update signature with parsed content - let parsed_signature = match serde_json::from_str(&parsed_cawg_json_string) { - Ok(parsed_signature) => parsed_signature, - Err(err) => { - return Err(crate::Error::JsonSerializationError(err.to_string())); - } - }; - assertion_data["signature"] = parsed_signature; - - // We don't need to show the padding fields either - let assertion_data_map = match assertion_data.as_object_mut() { - Some(assertion_data_map) => assertion_data_map, - None => { - return Err(crate::Error::JsonSerializationError( - "Could not parse CAWG assertion data as object".to_string(), - )); - } - }; - assertion_data_map.remove("pad1"); - assertion_data_map.remove("pad2"); - - Ok(()) -} - -/// Parse additional CAWG details from the manifest store to update displayed results. -/// As CAWG mostly async, this will block on network requests for checks using a tokio runtime. -fn get_cawg_details_for_manifest( - manifest: &c2pa::Manifest, - tokio_runtime: &Runtime, -) -> Option { - let ia_iter = IdentityAssertion::from_manifest(manifest); - - // TODO: Determine what should happen when multiple identities are reported (currently only 1 is supported) - let mut parsed_cawg_json = String::new(); - - ia_iter.for_each(|ia| { - let identity_assertion = match ia { - Ok(ia) => ia, - Err(err) => { - eprintln!("Could not parse CAWG identity assertion: {:?}", err); - return; - } - }; - - let isv = IcaSignatureVerifier {}; - let ica_validated = tokio_runtime.block_on(identity_assertion.validate(manifest, &isv)); - let ica = match ica_validated { - Ok(ica) => ica, - Err(err) => { - eprintln!("Could not validate CAWG identity assertion: {:?}", err); - return; - } - }; - - parsed_cawg_json = match serde_json::to_string(&ica) { - Ok(parsed_cawg_json) => parsed_cawg_json, - Err(err) => { - eprintln!( - "Could not parse CAWG identity claims aggregation details for manifest: {:?}", - err - ); - return; - } - }; - }); - - if parsed_cawg_json.is_empty() { - return None; - } - - // Get the JSON as mutable, so we can further parse and format CAWG data - let maybe_map = serde_json::from_str(parsed_cawg_json.as_str()); - let mut map: Map = match maybe_map { - Ok(map) => map, - Err(err) => { - eprintln!( - "Could not parse convert CAWG identity claims details to JSON string map: {:?}", - err - ); - return None; - } - }; - - // Get the credentials subject information... - let credentials_subject_maybe = map.get_mut("credentialSubject"); - let credentials_subject = match credentials_subject_maybe { - Some(credentials_subject) => credentials_subject, - None => { - eprintln!("Could not find credential subject in CAWG details for manifest"); - return None; - } - }; - let credentials_subject_as_obj = credentials_subject.as_object_mut(); - let credential_subject_details = match credentials_subject_as_obj { - Some(credentials_subject) => credentials_subject, - None => { - eprintln!("Could not parse credential subject as object in CAWG details for manifest"); - return None; - } - }; - // As per design CAWG has some repetition between assertion an signature (c2paAsset field) - // so we remove the c2paAsset field from the credential subject details too - credential_subject_details.remove("c2paAsset"); - - // return the for-display json-formatted string - let serialized_content = serde_json::to_string(&map); - match serialized_content { - Ok(serialized_content) => Some(serialized_content), - Err(err) => { - eprintln!("Could not parse CAWG details for manifest: {:?}", err); - None - } - } -} - fn main() -> Result<()> { let args = CliArgs::parse(); @@ -1041,7 +689,7 @@ fn main() -> Result<()> { }; let decorated_details_manifest = - decorate_json_detailled_display(reader, extracted_json_report, &tokio_runtime); + decorate_json_detailed_display(reader, extracted_json_report, &tokio_runtime); println!("{}", decorated_details_manifest); } else if let Some(Commands::Fragment { fragments_glob: Some(fg),