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: WIP branch for reading cawg data #904

Merged
merged 35 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
77e88c3
ci: Add debug statements
tmathern Feb 4, 2025
73b0d2a
Omit additional paddings
tmathern Feb 4, 2025
62e1a8b
feat: Omit more padding tags
tmathern Feb 4, 2025
9d4609d
ci: add explanation
tmathern Feb 4, 2025
2d8018d
ci: Catch result strings before they get returned
tmathern Feb 4, 2025
a699913
ci: sdk catch result string before returning them
tmathern Feb 4, 2025
823eecd
ci: look into parsing stuff
tmathern Feb 5, 2025
66342fd
ci: WIP for cawg display
tmathern Feb 5, 2025
c24a355
ci: Replace signature with hash
tmathern Feb 5, 2025
c97321f
ci: Replace signature with hash
tmathern Feb 5, 2025
2443c75
ci: Clean up leftover debug code
tmathern Feb 6, 2025
06ae720
ci: Parse cawg data in cli, introduce tokio
tmathern Feb 6, 2025
44f1b69
ci: WIP
tmathern Feb 6, 2025
7efd8fe
ci: WIP
tmathern Feb 6, 2025
a26492a
ci: Up some deps
tmathern Feb 6, 2025
3d2f4d8
ci: WIP
tmathern Feb 6, 2025
ed90f37
ci: WIP
tmathern Feb 6, 2025
febdffc
feat: Add methods to reader to decorate the json
tmathern Feb 6, 2025
fd3840d
ci: Got the cleaned content signature
tmathern Feb 6, 2025
736722f
ci: Some clean up
tmathern Feb 6, 2025
4d8b699
ci: Some clean up
tmathern Feb 6, 2025
406f20c
ci: WIP
tmathern Feb 6, 2025
3fe9468
feat: cawg parsing hack
tmathern Feb 6, 2025
9e6a20d
feat: move display
tmathern Feb 6, 2025
e632dd9
Merge branch 'mathern/cawg-reading-trois' into mathern/cawg-reading-deux
tmathern Feb 6, 2025
63c483e
feat: Refactor
tmathern Feb 6, 2025
5a86494
feat: Refactor
tmathern Feb 6, 2025
5285258
feat: Refactor
tmathern Feb 6, 2025
f73800a
ci: Tmp check all other tests
tmathern Feb 6, 2025
5ec42e5
ci: Tmp check all other tests
tmathern Feb 6, 2025
f2c6136
Debug
tmathern Feb 6, 2025
bd0fb29
fix: fix mistake of returning too early
tmathern Feb 6, 2025
39d5513
fix: Remove debug file
tmathern Feb 6, 2025
61f658f
fix: Remove debug file
tmathern Feb 6, 2025
f95aab1
fix: Change error handling
tmathern Feb 6, 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
14 changes: 13 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,22 @@ 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>> {
let reader_as_json = self.json();
let reader_as_json_str = reader_as_json.as_str();
let mapped_json = serde_json::from_str(reader_as_json_str);
match mapped_json {
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