c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

commit c259190fe63592296abc3f611ecb840dec5cda3a
parent ed3c238aebfc31d4c19a140d563aa70fdac0b25e
Author: Gavin  Peacock <gpeacock@adobe.com>
Date:   Tue,  7 Mar 2023 15:06:50 -0800

Update Validation Status codes (#200)

* update validation_status codes to 1.2 spec

* Add ingredient validation status MANIFEST_INACCESSIBLE
Diffstat:
Msdk/src/ingredient.rs | 25++++++++++++++++++++++++-
Msdk/src/validation_status.rs | 60++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Asdk/tests/fixtures/cloudx.jpg | 0
3 files changed, 76 insertions(+), 9 deletions(-)

diff --git a/sdk/src/ingredient.rs b/sdk/src/ingredient.rs @@ -460,7 +460,10 @@ impl Ingredient { #[cfg(feature = "file_io")] fn from_file_impl(path: &Path, options: &dyn IngredientOptions) -> Result<Self> { // these are declared inside this function in order to isolate them for wasm builds - use crate::status_tracker::{log_item, DetailedStatusTracker, StatusTracker}; + use crate::{ + status_tracker::{log_item, DetailedStatusTracker, StatusTracker}, + validation_status::MANIFEST_INACCESSIBLE, + }; #[cfg(feature = "diagnostics")] let _t = crate::utils::time_it::TimeIt::new("Ingredient:from_file_with_options"); @@ -552,6 +555,13 @@ impl Ingredient { | Err(Error::ProvenanceMissing) | Err(Error::UnsupportedType) => {} // no claims but valid file Err(Error::BadParam(desc)) if desc == *"unrecognized file type" => {} + Err(Error::RemoteManifestFetch(e)) => { + let mut vs = ValidationStatus::new(MANIFEST_INACCESSIBLE).set_explanation(e); + if let Some(url) = ingredient.provenance() { + vs = vs.set_url(url.to_string()); + } + ingredient.validation_status = Some([vs].to_vec()) + } Err(e) => { // we can ignore the error here because it should have a log entry corresponding to it debug!("ingredient {:?}", e); @@ -1080,6 +1090,19 @@ mod tests_file_io { } #[test] + #[cfg(feature = "fetch_remote_manifests")] + fn test_jpg_cloud_failure() { + let ap = fixture_path("cloudx.jpg"); + let ingredient = Ingredient::from_file(ap).expect("from_file"); + println!("ingredient = {ingredient}"); + assert!(ingredient.validation_status().is_some()); + assert_eq!( + ingredient.validation_status().unwrap()[0].code(), + validation_status::MANIFEST_INACCESSIBLE + ); + } + + #[test] #[cfg(feature = "file_io")] fn test_jpg_with_path() { let ap = fixture_path("CIE-sig-CA.jpg"); diff --git a/sdk/src/validation_status.rs b/sdk/src/validation_status.rs @@ -96,10 +96,11 @@ impl ValidationStatus { match error { e if e.starts_with("ClaimMissing") => CLAIM_MISSING, e if e.starts_with("AssertionMissing") => ASSERTION_MISSING, - e if e.starts_with("AssertionDecoding") => STATUS_ASSERTION_MALFORMED, // todo: no code for invalid assertion format + e if e.starts_with("AssertionDecoding") => ASSERTION_REQUIRED_MISSING, e if e.starts_with("HashMismatch") => ASSERTION_DATAHASH_MATCH, + e if e.starts_with("RemoteManifestFetch") => MANIFEST_INACCESSIBLE, e if e.starts_with("PrereleaseError") => STATUS_PRERELEASE, - _ => STATUS_OTHER, + _ => GENERAL_ERROR, } } @@ -108,10 +109,11 @@ impl ValidationStatus { match error { Error::ClaimMissing { .. } => CLAIM_MISSING, Error::AssertionMissing { .. } => ASSERTION_MISSING, - Error::AssertionDecoding(_code) => STATUS_ASSERTION_MALFORMED, // todo: no code for invalid assertion format + Error::AssertionDecoding(_code) => ASSERTION_REQUIRED_MISSING, //todo detect json/cbor errors Error::HashMismatch(_) => ASSERTION_DATAHASH_MATCH, + Error::RemoteManifestFetch(_) => MANIFEST_INACCESSIBLE, Error::PrereleaseError => STATUS_PRERELEASE, - _ => STATUS_OTHER, + _ => GENERAL_ERROR, } } @@ -263,6 +265,16 @@ pub const CLAIM_MULTIPLE: &str = "claim.multiple"; /// `ValidationStatus.url()` will point to a C2PA claim box. pub const HARD_BINDINGS_MISSING: &str = "claim.hardBindings.missing"; +/// A required field is not present in the claim. +/// +/// `ValidationStatus.url()` will point to a C2PA claim box. +pub const CLAIM_REQUIRED_MISSING: &str = "claim.required.missing"; + +/// The cbor of the claim is not valid. +/// +/// `ValidationStatus.url()` will point to a C2PA claim box. +pub const CLAIM_CBOR_INVALID: &str = "claim.cbor.invalid"; + /// The hash of the the referenced ingredient claim in the manifest /// does not match the corresponding hash in the ingredient's hashed /// URI in the claim. @@ -282,6 +294,14 @@ pub const CLAIM_SIGNATURE_MISSING: &str = "claimSignature.missing"; /// `ValidationStatus.url()` will point to a C2PA claim signature box. pub const CLAIM_SIGNATURE_MISMATCH: &str = "claimSignature.mismatch"; +/// If a manifest was documented to exist in a remote location, +/// but is not present there, or the location is not currently available +/// (such as in an offline scenario), +/// the `manifest.inaccessible` error code shall be used to report the situation. +/// +/// `ValidationStatus.url()` URI reference to the C2PA Manifest that could not be accessed. +pub const MANIFEST_INACCESSIBLE: &str = "manifest.inaccessible"; + /// The manifest has more than one ingredient whose `relationship` /// is `parentOf`. /// @@ -370,6 +390,27 @@ pub const ASSERTION_NOT_REDACTED: &str = "assertion.notRedacted"; /// `ValidationStatus.url()` will point to a C2PA claim box. pub const ASSERTION_SELF_REDACTED: &str = "assertion.selfRedacted"; +/// A required field is not present in an assertion. +/// +/// `ValidationStatus.url()` will point to a C2PA assertion. +pub const ASSERTION_REQUIRED_MISSING: &str = "assertion.required.missing"; + +/// The JSON(-LD) of an assertion is not valid. +/// +/// `ValidationStatus.url()` will point to a C2PA assertion. +pub const ASSERTION_JSON_INVALID: &str = "assertion.json.invalid"; + +/// The cbor of an assertion is not valid. +/// +/// `ValidationStatus.url()` will point to a C2PA assertion. +pub const ASSERTION_CBOR_INVALID: &str = "assertion.cbor.invalid"; + +/// An action that requires an associated ingredient either does not have one +/// or the one specified cannot be located +/// +/// `ValidationStatus.url()` will point to a C2PA assertion. +pub const ACTION_ASSERTION_INGREDIENT_MISMATCH: &str = "assertion.action.ingredientMismatch"; + /// An `action` assertion was redacted when the ingredient's /// claim was created. /// @@ -391,13 +432,13 @@ pub const ASSERTION_BMFFHASH_MISMATCH: &str = "assertion.bmffHash.mismatch"; /// A hard binding assertion is in a cloud data assertion. /// /// `ValidationStatus.url()` will point to a C2PA assertion. -pub const ASSERTION_CLOUDDATA_HARD_BINDING: &str = "assertion.clouddata.hardBinding"; +pub const ASSERTION_CLOUD_DATA_HARD_BINDING: &str = "assertion.cloud-data.hardBinding"; /// An update manifest contains a cloud data assertion referencing /// an actions assertion. /// /// `ValidationStatus.url()` will point to a C2PA assertion. -pub const ASSERTION_CLOUDDATA_ACTIONS: &str = "assertion.clouddata.actions"; +pub const ASSERTION_CLOUD_DATA_ACTIONS: &str = "assertion.cloud-data.actions"; /// The value of an `alg` header, or other header that specifies an /// algorithm used to compute the value of another field, is unknown @@ -406,11 +447,14 @@ pub const ASSERTION_CLOUDDATA_ACTIONS: &str = "assertion.clouddata.actions"; /// `ValidationStatus.url()` will point to a C2PA claim box or C2PA assertion. pub const ALGORITHM_UNSUPPORTED: &str = "algorithm.unsupported"; +/// A value to be used when there was an error not specifically listed here. +/// +/// `ValidationStatus.url()` will point to a C2PA claim box or C2PA assertion. +pub const GENERAL_ERROR: &str = "general.error"; + // -- unofficial status codes -- -pub(crate) const STATUS_OTHER: &str = "com.adobe.other"; pub(crate) const STATUS_PRERELEASE: &str = "com.adobe.prerelease"; -pub(crate) const STATUS_ASSERTION_MALFORMED: &str = "com.adobe.assertion.malformed"; /// Returns `true` if the status code is a known C2PA success status code. /// diff --git a/sdk/tests/fixtures/cloudx.jpg b/sdk/tests/fixtures/cloudx.jpg Binary files differ.