Skip to content

Commit

Permalink
feat: Move cawg parsing for c2patool into own file
Browse files Browse the repository at this point in the history
  • Loading branch information
tmathern committed Feb 7, 2025
1 parent aba8668 commit 85201a7
Show file tree
Hide file tree
Showing 2 changed files with 373 additions and 355 deletions.
370 changes: 370 additions & 0 deletions cli/src/cawg_parser.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
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<String, Value> = 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<String, Value> = 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()
}
}
}
Loading

0 comments on commit 85201a7

Please sign in to comment.