Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update c2patool to read CAWG data #907

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b1990a3
feat: c2patool to read cawg data (#904)
tmathern Feb 6, 2025
3521730
ci: Refactor
tmathern Feb 6, 2025
9af655d
ci: Refactor
tmathern Feb 6, 2025
7ed719c
ci: Refactor
tmathern Feb 6, 2025
a18e1f9
ci: Refactor and rerun tests
tmathern Feb 6, 2025
cb389d5
ci: Refactor
tmathern Feb 6, 2025
e1b268e
ci: Solidify error handling
tmathern Feb 6, 2025
cc6e7bd
ci: Add test
tmathern Feb 6, 2025
aba1ef6
ci: Merge branch 'main' into mathern/cawg-reading-trois
tmathern Feb 6, 2025
8a2ead2
ci: Prepare for detailed view
tmathern Feb 6, 2025
dd70c83
ci: Retrigger
tmathern Feb 6, 2025
a77004e
ci: Remove debug files
tmathern Feb 6, 2025
13875f4
ci: Review comment 1
tmathern Feb 7, 2025
3b45088
ci: Review comment 2
tmathern Feb 7, 2025
49adfd2
ci: Retrigger checks
tmathern Feb 7, 2025
151e312
ci: Retrigger checks
tmathern Feb 7, 2025
928b07b
ci: Merge branch 'main' into mathern/cawg-reading-trois
tmathern Feb 7, 2025
153be98
ci: Cargo lock too
tmathern Feb 7, 2025
c32ec31
Errors go to stdout
tmathern Feb 7, 2025
aba8668
feat: c2patool to read cawg data with `--detailed` flag too (#909)
tmathern Feb 7, 2025
85201a7
feat: Move cawg parsing for c2patool into own file
tmathern Feb 7, 2025
077a285
fix: Overhaul of error handling
tmathern Feb 7, 2025
b9c5c7a
fix: Update error handling for non detailed view too
tmathern Feb 7, 2025
5cffc27
fix: Format
tmathern Feb 7, 2025
ea92320
ci: Clarify comment
tmathern Feb 7, 2025
b8ad835
fix: Refactor
tmathern Feb 7, 2025
346f040
ci: Format
tmathern Feb 7, 2025
dc48058
fix: Add test
tmathern Feb 7, 2025
2a7d66d
fix: Add test
tmathern Feb 7, 2025
7d2418a
ci: Some more refactor
tmathern Feb 7, 2025
1c262b2
fix: Clarify comments
tmathern Feb 7, 2025
772570a
ci: Refactor
tmathern Feb 7, 2025
18f1a0d
ci: Refactor
tmathern Feb 7, 2025
7b74080
fix: No need to process stuff we don't use
tmathern Feb 7, 2025
9a12f0f
fix: Remove debug code
tmathern Feb 7, 2025
2b7e13d
fix: Remove some more stuff
tmathern Feb 7, 2025
fdd1906
ci: Undo new error
tmathern Feb 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ test-wasm:
test-wasm-web:
cd sdk && wasm-pack test --chrome --headless -- --features="serialize_thumbnails"

test-no-wasm: check-format check-docs clippy test-local

# Full local validation, build and test all features including wasm
# Run this before pushing a PR to pre-validate
test: check-format check-docs clippy test-local test-wasm-web
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ impl SignatureVerifier for IcaSignatureVerifier {
));
};

dbg!(&jwk_prop);
// dbg!(&jwk_prop);

// OMG SO HACKY!
let Ok(jwk_json) = serde_json::to_string_pretty(jwk_prop) else {
Expand Down
2 changes: 1 addition & 1 deletion cawg_identity/src/claim_aggregation/w3c_vc/did_web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub(crate) async fn resolve(did: &Did<'_>) -> Result<DidDocument, DidWebError> {

let method_specific_id = did.method_specific_id();

dbg!(method_specific_id);
//dbg!(method_specific_id);

let url = to_url(method_specific_id)?;
// TODO: https://w3c-ccg.github.io/did-method-web/#in-transit-security
Expand Down
2 changes: 0 additions & 2 deletions cawg_identity/src/claim_aggregation/w3c_vc/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ pub(crate) mod one_or_many {
where
M: de::MapAccess<'de>,
{
eprintln!("Yo!");

let one = Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;

Ok(nev!(one))
Expand Down
2 changes: 2 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test)'] }
[dependencies]
anyhow = "1.0"
atree = "0.5.2"
cawg-identity = { path = "../cawg_identity"}
c2pa = { path = "../sdk", version = "0.45.1", features = [
"fetch_remote_manifests",
"file_io",
Expand All @@ -37,6 +38,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
serde_json = "1.0"
tempfile = "3.3"
tokio = { version = "1.42", features = ["full"] }
treeline = "0.1.0"
pem = "3.0.3"
openssl = { version = "0.10.61", features = ["vendored"] }
Expand Down
155 changes: 150 additions & 5 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ 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::{
Expand Down Expand Up @@ -424,6 +427,144 @@ fn verify_fragmented(init_pattern: &Path, frag_pattern: &Path) -> Result<Vec<Rea
Ok(readers)
}

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(_) => {
println!("Could not parse manifest store JSON content");
return String::new();
}
};

let json_content = match reader_content.get_mut("manifests") {
Some(json) => json,
None => {
println!("No JSON to parse in manifest store (key: manifests)");
return String::new();
}
};

// Update manifests with CAWG details
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: &c2pa::Manifest = reader.get_manifest(key).unwrap();

// Replace the signature content of the assertion with parsed details by CAWG
let assertions = value.get_mut("assertions").unwrap();
let assertions_array = assertions.as_array_mut().unwrap();
for assertion in assertions_array {
let label = assertion.get("label").unwrap().to_string();

// for CAWG assertions, further parse the signature
if label.contains("cawg.identity") {
let parsed_cawg_json_string =
match get_cawg_details_for_manifest(current_manifest, tokio_runtime) {
Some(parsed_cawg_json_string) => parsed_cawg_json_string,
None => {
println!("Could not parse CAWG details for manifest");
continue;
}
};

let assertion_data: &mut serde_json::Value = assertion.get_mut("data").unwrap();
assertion_data["signature"] =
serde_json::from_str(&parsed_cawg_json_string).unwrap();
}
}
}
} else {
println!("Could not parse manifest store JSON content");
}

match serde_json::to_string_pretty(&reader_content) {
Ok(decorated_result) => decorated_result,
Err(err) => {
println!(
"Could not parse manifest store JSON content with additional CAWG details: {:?}",
err
);
String::new()
}
}
}

/// 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) => {
println!("Could not parse 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) => {
println!("Could not validate identity assertion: {:?}", err);
return;
}
};

parsed_cawg_json = serde_json::to_string(&ica).unwrap();
});

// Get the JSON as mutable, so we can further parse and format
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) => {
println!("Could not parse CAWG details for manifest: {:?}", err);
return None;
}
};

// Get the credentials subject information...
let credentials_subject = map.get_mut("credentialSubject");
let credentials_subject = match credentials_subject {
Some(credentials_subject) => credentials_subject,
None => {
println!("Could not find credentialSubject 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 => {
println!("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) => {
println!("Could not parse CAWG details for manifest: {:?}", err);
None
}
}
}

fn main() -> Result<()> {
let args = CliArgs::parse();

Expand Down Expand Up @@ -464,6 +605,9 @@ fn main() -> Result<()> {
// configure the SDK
configure_sdk(&args).context("Could not configure c2pa-rs")?;

// configure tokio runtime for blocking operations
let tokio_runtime: Runtime = Runtime::new()?;

// Remove manifest needs to also remove XMP provenance
// if args.remove_manifest {
// match args.output {
Expand Down Expand Up @@ -674,10 +818,9 @@ fn main() -> Result<()> {
Ingredient::from_file(&args.path).map_err(special_errs)?
)
} else if args.detailed {
println!(
"{:#?}",
Reader::from_file(&args.path).map_err(special_errs)?
)
println!("## TMN-Debug ~ cli#main ~ Here we read the detailed edition");
let reader = Reader::from_file(&args.path).map_err(special_errs)?;
println!("{:#?}", reader)
} else if let Some(Commands::Fragment {
fragments_glob: Some(fg),
}) = &args.command
Expand All @@ -689,7 +832,9 @@ fn main() -> Result<()> {
println!("{} Init manifests validated", stores.len());
}
} else {
println!("{}", Reader::from_file(&args.path).map_err(special_errs)?)
let reader: Reader = Reader::from_file(&args.path).map_err(special_errs)?;
let stringified_decorated_json = decorate_json_display(reader, &tokio_runtime);
println!("{}", stringified_decorated_json);
}

Ok(())
Expand Down
Binary file added cli/tests/fixtures/C_with_CAWG_data.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ serde_bytes = "0.11.5"
serde_cbor = "0.11.1"
serde_derive = "1.0.197"
serde_json = { version = "1.0.117", features = ["preserve_order"] }
serde_with = "3.11.0"
serde_with = { version = "3.12.0" }
serde-transcode = "1.1.1"
sha1 = "0.10.6"
sha2 = "0.10.6"
Expand Down
3 changes: 3 additions & 0 deletions sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ pub enum Error {
#[error("The Verifiable Content structure is not valid")]
VerifiableCredentialInvalid,

#[error("error while serializing to JSON: {0}")]
JsonSerializationError(String),

/// Could not parse ECDSA signature. (Only appears when using WASM web crypto.)
#[error("could not parse ECDSA signature")]
InvalidEcdsaSignature,
Expand Down
6 changes: 3 additions & 3 deletions sdk/src/hashed_uri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ mod tests {
fn impl_clone() {
let h = HashedUri::new(
"self#jumbf=c2pa/urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4/c2pa.assertions/c2pa.hash.data".to_owned(),
Some("sha256".to_owned()),
Some("sha256".to_owned()),
&hex!("53d1b2cf4e6d9a97ed9281183fa5d836c32751b9d2fca724b40836befee7d67f"),
);

Expand All @@ -124,7 +124,7 @@ mod tests {
fn impl_debug() {
let h = HashedUri::new(
"self#jumbf=c2pa/urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4/c2pa.assertions/c2pa.hash.data".to_owned(),
Some("sha256".to_owned()),
Some("sha256".to_owned()),
&hex!("53d1b2cf4e6d9a97ed9281183fa5d836c32751b9d2fca724b40836befee7d67f"),
);

Expand All @@ -135,7 +135,7 @@ mod tests {
fn impl_display() {
let h = HashedUri::new(
"self#jumbf=c2pa/urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4/c2pa.assertions/c2pa.hash.data".to_owned(),
Some("sha256".to_owned()),
Some("sha256".to_owned()),
&hex!("53d1b2cf4e6d9a97ed9281183fa5d836c32751b9d2fca724b40836befee7d67f"),
);

Expand Down
6 changes: 6 additions & 0 deletions sdk/src/manifest_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,13 @@ impl std::fmt::Display for ManifestStore {
}

json = b64_tag(json, "hash");

// list of tags to omit (padding tags)
// Reason of padding, see note at:
// https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_going_back_and_filling_in
json = omit_tag(json, "pad");
json = omit_tag(json, "pad1");
json = omit_tag(json, "pad2");

f.write_str(&json)
}
Expand Down
11 changes: 10 additions & 1 deletion sdk/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use c2pa_status_tracker::DetailedStatusTracker;
#[cfg(feature = "json_schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

#[cfg(feature = "file_io")]
use crate::error::Error;
Expand Down Expand Up @@ -241,11 +242,19 @@ impl Reader {
})
}

/// Get the manifest store as a JSON string
/// Get the manifest store as a JSON string.
pub fn json(&self) -> String {
self.manifest_store.to_string()
}

/// Get the manifest store as a serde serialized JSON value map.
pub fn json_value_map(&self) -> Result<Map<String, Value>> {
match serde_json::from_str(self.json().as_str()) {
Ok(mapped_json) => Ok(mapped_json),
Err(err) => Err(crate::Error::JsonSerializationError(err.to_string())),
}
}

/// Get the [`ValidationStatus`] array of the manifest store if it exists.
/// Call this method to check for validation errors.
///
Expand Down
Loading