commit 66a872d37239f7833512da725dc1a1ba4fe34ba7
parent 089f27f2e8b2791cc9bf5c1ee85eb41f38a51b37
Author: mauricefisher64 <92736594+mauricefisher64@users.noreply.github.com>
Date: Mon, 6 Feb 2023 08:54:36 -0500
Update Ingredient and VC hashes to 1.2 spec (#184)
* 1.2 manifest hash support
1.2 VC hash support
Salt VC for 1.2 redaction support
* cleanup and unit test
* cleanup comments
* Fix function name typo
* Make sure we preserve salt for all paths by adding to HashedUri
Diffstat:
6 files changed, 301 insertions(+), 104 deletions(-)
diff --git a/sdk/src/claim.rs b/sdk/src/claim.rs
@@ -23,7 +23,11 @@ use crate::{
get_thumbnail_image_type, get_thumbnail_instance, get_thumbnail_type, Assertion,
AssertionBase, AssertionData,
},
- assertions::{self, labels, BmffHash, DataHash},
+ assertions::{
+ self,
+ labels::{self, CLAIM},
+ BmffHash, DataHash,
+ },
cose_validator::{get_signing_info, verify_cose, verify_cose_async},
error::{Error, Result},
hashed_uri::HashedUri,
@@ -32,8 +36,9 @@ use crate::{
boxes::{
CAICBORAssertionBox, CAIJSONAssertionBox, CAIUUIDAssertionBox, JumbfEmbeddedFileBox,
},
+ labels::{ASSERTIONS, CREDENTIALS, SIGNATURE},
},
- salt::{SaltGenerator, NO_SALT},
+ salt::{DefaultSalt, SaltGenerator, NO_SALT},
status_tracker::{log_item, OneShotStatusTracker, StatusTracker},
utils::hash_utils::{hash_by_alg, vec_compare, verify_by_alg},
validation_status,
@@ -200,7 +205,7 @@ pub struct Claim {
// Internal list of verifiable credentials for claim.
// These are serialized manually based on need.
#[serde(skip_deserializing, skip_serializing)]
- vc_store: Vec<AssertionData>,
+ vc_store: Vec<(HashedUri, AssertionData)>,
claim_generator: String, // generator of this claim
@@ -211,6 +216,10 @@ pub struct Claim {
#[serde(skip_deserializing, skip_serializing)]
original_bytes: Option<Vec<u8>>,
+ // original JUMBF box order need to recalculate JUMBF box hash
+ #[serde(skip_deserializing, skip_serializing)]
+ original_box_order: Option<Vec<&'static str>>,
+
#[serde(skip_serializing_if = "Option::is_none")]
redacted_assertions: Option<Vec<String>>, // list of redacted assertions
@@ -294,6 +303,7 @@ impl Claim {
vc_store: Vec::new(),
assertions: Vec::new(),
original_bytes: None,
+ original_box_order: None,
redacted_assertions: None,
alg: Some(BUILD_HASH_ALG.to_string()),
alg_soft: None,
@@ -325,6 +335,7 @@ impl Claim {
vc_store: Vec::new(),
assertions: Vec::new(),
original_bytes: None,
+ original_box_order: None,
redacted_assertions: None,
alg: Some(BUILD_HASH_ALG.into()),
alg_soft: None,
@@ -413,6 +424,22 @@ impl Claim {
self.title.as_ref()
}
+ /// order for which to generate the JUMBF boxes with writing manifest
+ pub fn set_box_order(&mut self, box_order: Vec<&'static str>) {
+ self.original_box_order = Some(box_order);
+ }
+
+ /// order to process
+ pub fn get_box_order(&self) -> &[&str] {
+ const DEFAULT_MANIFEST_ORDER: [&str; 4] = [ASSERTIONS, CLAIM, SIGNATURE, CREDENTIALS];
+
+ if let Some(bo) = &self.original_box_order {
+ bo
+ } else {
+ &DEFAULT_MANIFEST_ORDER
+ }
+ }
+
/// get algorithm
pub fn alg(&self) -> &str {
match self.alg.as_ref() {
@@ -503,7 +530,7 @@ impl Claim {
self.claim_generator_hints.as_ref()
}
- pub fn calc_box_hash(
+ pub fn calc_assertion_box_hash(
label: &str,
assertion: &Assertion,
salt: Option<Vec<u8>>,
@@ -578,13 +605,14 @@ impl Claim {
// Get salted hash of the assertion's contents.
let salt = salt_generator.generate_salt();
- let hash = Claim::calc_box_hash(&as_label, &assertion, salt.clone(), self.alg())?;
+ let hash = Claim::calc_assertion_box_hash(&as_label, &assertion, salt.clone(), self.alg())?;
// Build hash link.
let link = jumbf::labels::to_assertion_uri(self.label(), &as_label);
let link_relative = jumbf::labels::to_relative_uri(&link);
- let c2pa_assertion = C2PAAssertion::new(link_relative, None, &hash);
+ let mut c2pa_assertion = C2PAAssertion::new(link_relative, None, &hash);
+ c2pa_assertion.add_salt(salt.clone());
// Add to assertion store.
let (_l, instance) = Claim::assertion_label_from_link(&as_label);
@@ -623,21 +651,56 @@ impl Claim {
// the "id" value will be used as the label in the vcstore
pub fn add_verifiable_credential(&mut self, vc_json: &str) -> Result<HashedUri> {
let id = Claim::vc_id(vc_json)?;
-
- let hash = hash_by_alg(self.alg(), vc_json.as_bytes(), None);
+ let credential = AssertionData::Json(vc_json.to_string());
let link = jumbf::labels::to_verifiable_credential_uri(self.label(), &id);
- let c2pa_assertion = C2PAAssertion::new(link, Some(self.alg().to_string()), &hash);
+ // salt box for 1.2 VC redaction support
+ let ds = DefaultSalt::default();
+ let salt = ds.generate_salt();
+
+ // assertion JUMBF box hash for 1.2 validation
+ let assertion = Assertion::from_data_json(&id, vc_json.as_bytes())?;
+ let hash = Claim::calc_assertion_box_hash(&id, &assertion, salt.clone(), self.alg())?;
+
+ let mut c2pa_assertion = C2PAAssertion::new(link, Some(self.alg().to_string()), &hash);
+ c2pa_assertion.add_salt(salt);
// add credential to vcstore
- let credential = AssertionData::Json(vc_json.to_string());
- self.vc_store.push(credential);
+ self.vc_store.push((c2pa_assertion.clone(), credential));
Ok(c2pa_assertion)
}
- pub fn get_verifiable_credentials(&self) -> &Vec<AssertionData> {
+ /// Load known VC with optional salt
+ pub fn put_verifiable_credential(
+ &mut self,
+ vc_json: &str,
+ salt: Option<Vec<u8>>,
+ ) -> Result<()> {
+ let id = Claim::vc_id(vc_json)?;
+ let credential = AssertionData::Json(vc_json.to_string());
+
+ let link = jumbf::labels::to_verifiable_credential_uri(self.label(), &id);
+
+ // assertion JUMBF box hash for 1.2 validation
+ let assertion = Assertion::from_data_json(&id, vc_json.as_bytes())?;
+ let hash = Claim::calc_assertion_box_hash(&id, &assertion, salt.clone(), self.alg())?;
+
+ let mut c2pa_assertion = C2PAAssertion::new(link, Some(self.alg().to_string()), &hash);
+ c2pa_assertion.add_salt(salt);
+
+ // add credential to vcstore
+ self.vc_store.push((c2pa_assertion, credential));
+
+ Ok(())
+ }
+
+ pub fn get_verifiable_credentials(&self) -> Vec<&AssertionData> {
+ self.vc_store.iter().map(|t| &t.1).collect::<Vec<_>>()
+ }
+
+ pub fn get_verifiable_credentials_store(&self) -> &Vec<(HashedUri, AssertionData)> {
&self.vc_store
}
@@ -669,7 +732,7 @@ impl Claim {
data_hash.pad_to_size(original_len)?;
replacement_assertion = data_hash.to_assertion()?;
- let replacement_hash = Claim::calc_box_hash(
+ let replacement_hash = Claim::calc_assertion_box_hash(
&dh_assertion.label(),
&replacement_assertion,
dh_assertion.salt().clone(),
@@ -709,7 +772,7 @@ impl Claim {
Some(ref mut bmff_assertion) => {
let original_hash = bmff_assertion.hash().to_vec();
- let replacement_hash = Claim::calc_box_hash(
+ let replacement_hash = Claim::calc_assertion_box_hash(
&bmff_assertion.label(),
&replacement_assertion,
bmff_assertion.salt().clone(),
diff --git a/sdk/src/hashed_uri.rs b/sdk/src/hashed_uri.rs
@@ -24,6 +24,10 @@ pub struct HashedUri {
alg: Option<String>,
#[serde(with = "serde_bytes")]
hash: Vec<u8>, // hash stored as cbor byte string
+
+ // salt used to generate hash
+ #[serde(skip_deserializing, skip_serializing)]
+ salt: Option<Vec<u8>>,
}
impl HashedUri {
@@ -32,6 +36,7 @@ impl HashedUri {
url,
alg,
hash: hash_bytes.to_vec(),
+ salt: None,
}
}
@@ -54,6 +59,14 @@ impl HashedUri {
pub(crate) fn update_hash(&mut self, hash: Vec<u8>) {
self.hash = hash;
}
+
+ pub fn add_salt(&mut self, salt: Option<Vec<u8>>) {
+ self.salt = salt;
+ }
+
+ pub fn salt(&self) -> &Option<Vec<u8>> {
+ &self.salt
+ }
}
impl fmt::Display for HashedUri {
diff --git a/sdk/src/ingredient.rs b/sdk/src/ingredient.rs
@@ -687,7 +687,8 @@ impl Ingredient {
};
// have Store check and load ingredients and add them to a claim
- Store::load_ingredient_to_claim(claim, &manifest_label, &buffer, redactions)?;
+ let ingredient_store =
+ Store::load_ingredient_to_claim(claim, &manifest_label, &buffer, redactions)?;
// get the ingredient map loaded in previous
match claim.claim_ingredient(&manifest_label) {
@@ -697,7 +698,9 @@ impl Ingredient {
.iter()
.find(|c| c.label() == manifest_label)
{
- let hash = ingredient_active_claim.hash();
+ let hash =
+ ingredient_store.get_manifest_box_hash(ingredient_active_claim); // get C2PA 1.2 JUMBF box hash
+
let uri = jumbf::labels::to_manifest_uri(&manifest_label);
// if there are validations and they have all passed, then use the parent claim thumbnail if available
diff --git a/sdk/src/jumbf/boxes.rs b/sdk/src/jumbf/boxes.rs
@@ -1384,6 +1384,10 @@ impl CAIStore {
// we REALLY want to return a CAIAssertionStore but can't do to referencing...
self.store.data_box_as_superbox(0)
}
+
+ pub fn set_salt(&mut self, salt: Vec<u8>) -> JumbfParseResult<()> {
+ self.store.desc_box.set_salt(salt)
+ }
}
// ANCHOR CAI Block
@@ -2070,7 +2074,14 @@ impl BoxReader {
None => (buf, None),
}
}
- _ => (buf, None),
+ _ => {
+ // we do not store the trailing 0 on load
+ if buf[buf.len() - 1] == 0 {
+ buf.pop();
+ }
+
+ (buf, None)
+ }
};
Ok(JUMBFEmbeddedFileDescriptionBox::from(
diff --git a/sdk/src/store.rs b/sdk/src/store.rs
@@ -23,31 +23,37 @@ use log::error;
use crate::embedded_xmp;
#[cfg(feature = "async_signer")]
use crate::AsyncSigner;
-#[cfg(feature = "sign")]
use crate::{
- assertion::AssertionData,
- assertions::DataHash,
- asset_io::{CAIReadWrite, HashBlockObjectType, HashObjectPositions},
- cose_sign::cose_sign,
- cose_validator::verify_cose,
- jumbf_io::{object_locations_from_stream, save_jumbf_to_stream},
- utils::{
- hash_utils::{hash256, Exclusion},
- patch::patch_bytes,
+ assertion::{
+ Assertion, AssertionBase, AssertionData, AssertionDecodeError, AssertionDecodeErrorCause,
+ },
+ assertions::{
+ labels::{self, CLAIM},
+ Ingredient, Relationship,
},
- Signer,
-};
-use crate::{
- assertion::{Assertion, AssertionBase, AssertionDecodeError, AssertionDecodeErrorCause},
- assertions::{labels, Ingredient, Relationship},
claim::{Claim, ClaimAssertion, ClaimAssetData},
error::{Error, Result},
hash_utils::{hash_by_alg, vec_compare, verify_by_alg},
- jumbf::{self, boxes::*},
+ jumbf::{
+ self,
+ boxes::*,
+ labels::{ASSERTIONS, CREDENTIALS, SIGNATURE},
+ },
jumbf_io::load_jumbf_from_memory,
status_tracker::{log_item, OneShotStatusTracker, StatusTracker},
+ utils::hash_utils::hash256,
validation_status, ManifestStoreReport,
};
+#[cfg(feature = "sign")]
+use crate::{
+ assertions::DataHash,
+ asset_io::{CAIReadWrite, HashBlockObjectType, HashObjectPositions},
+ cose_sign::cose_sign,
+ cose_validator::verify_cose,
+ jumbf_io::{object_locations_from_stream, save_jumbf_to_stream},
+ utils::{hash_utils::Exclusion, patch::patch_bytes},
+ Signer,
+};
#[cfg(feature = "file_io")]
use crate::{
assertions::{BmffHash, DataMap, ExclusionsMap, SubsetMap},
@@ -66,6 +72,7 @@ const MANIFEST_STORE_EXT: &str = "c2pa"; // file extension for external manifest
#[derive(Debug, PartialEq)]
pub struct Store {
claims_map: HashMap<String, usize>,
+ manifest_box_hash_cache: HashMap<String, Vec<u8>>,
claims: Vec<Claim>,
label: String,
provenance_path: Option<String>,
@@ -108,6 +115,7 @@ impl Store {
pub fn new_with_label(label: &str) -> Self {
Store {
claims_map: HashMap::new(),
+ manifest_box_hash_cache: HashMap::new(),
claims: Vec::new(),
label: label.to_string(),
provenance_path: None,
@@ -143,6 +151,15 @@ impl Store {
&self.claims
}
+ /// the JUMBF manifest box hash (spec 1.2)
+ pub fn get_manifest_box_hash(&self, claim: &Claim) -> Vec<u8> {
+ if let Some(bh) = self.manifest_box_hash_cache.get(claim.label()) {
+ bh.clone()
+ } else {
+ Store::calc_manifest_box_hash(claim, None, claim.alg()).unwrap_or(Vec::new())
+ }
+ }
+
/// Add a new Claim to this Store. The function
/// will return the label of the claim.
pub fn commit_claim(&mut self, mut claim: Claim) -> Result<String> {
@@ -324,8 +341,7 @@ impl Store {
// Returns placeholder that will be searched for and replaced
// with actual signature data.
- #[cfg(feature = "sign")]
- fn sign_claim_placeholder(&self, claim: &Claim, min_reserve_size: usize) -> Vec<u8> {
+ fn sign_claim_placeholder(claim: &Claim, min_reserve_size: usize) -> Vec<u8> {
let placeholder_str = format!("signature placeholder:{}", claim.label());
let mut placeholder = hash256(placeholder_str.as_bytes()).as_bytes().to_vec();
@@ -445,7 +461,6 @@ impl Store {
self.claims_map.insert(label, index);
}
- #[cfg(feature = "sign")]
fn add_assertion_to_jumbf_store(
store: &mut CAIAssertionStore,
claim_assertion: &ClaimAssertion,
@@ -536,7 +551,7 @@ impl Store {
.data_box_as_json_box(0)
.ok_or(Error::JumbfBoxNotFound)?;
let assertion = Assertion::from_data_json(&raw_label, json_box.json())?;
- let hash = Claim::calc_box_hash(label, &assertion, salt.clone(), &alg)?;
+ let hash = Claim::calc_assertion_box_hash(label, &assertion, salt.clone(), &alg)?;
Ok(ClaimAssertion::new(assertion, instance, &hash, &alg, salt))
}
CAI_EMBEDDED_FILE_UUID => {
@@ -549,7 +564,7 @@ impl Store {
let media_type = ef_box.media_type();
let assertion =
Assertion::from_data_binary(&raw_label, &media_type, data_box.data());
- let hash = Claim::calc_box_hash(label, &assertion, salt.clone(), &alg)?;
+ let hash = Claim::calc_assertion_box_hash(label, &assertion, salt.clone(), &alg)?;
Ok(ClaimAssertion::new(assertion, instance, &hash, &alg, salt))
}
CAI_CBOR_ASSERTION_UUID => {
@@ -557,7 +572,7 @@ impl Store {
.data_box_as_cbor_box(0)
.ok_or(Error::JumbfBoxNotFound)?;
let assertion = Assertion::from_data_cbor(&raw_label, cbor_box.cbor());
- let hash = Claim::calc_box_hash(label, &assertion, salt.clone(), &alg)?;
+ let hash = Claim::calc_assertion_box_hash(label, &assertion, salt.clone(), &alg)?;
Ok(ClaimAssertion::new(assertion, instance, &hash, &alg, salt))
}
CAI_UUID_ASSERTION_UUID => {
@@ -567,7 +582,7 @@ impl Store {
let uuid_str = hex::encode(uuid_box.uuid());
let assertion = Assertion::from_data_uuid(&raw_label, &uuid_str, uuid_box.data());
- let hash = Claim::calc_box_hash(label, &assertion, salt.clone(), &alg)?;
+ let hash = Claim::calc_assertion_box_hash(label, &assertion, salt.clone(), &alg)?;
Ok(ClaimAssertion::new(assertion, instance, &hash, &alg, salt))
}
_ => Err(Error::JumbfCreationError),
@@ -614,77 +629,117 @@ impl Store {
// Add claims and assertions in this store to the JUMBF store.
for claim in &self.claims {
- let label = claim.label();
-
- let mut cai_store = CAIStore::new(label, claim.update_manifest());
+ let cai_store = Store::build_manifest_box(claim, min_reserve_size)?;
- // Add claim box. Note the order of the boxes are set by the spec
- let mut cb = CAIClaimBox::new();
+ // add the completed CAI store into the CAI block.
+ cai_block.add_box(Box::new(cai_store));
+ }
- // Create the CAI assertion store.
- let mut a_store = CAIAssertionStore::new();
+ // Write it to memory.
+ let mut mem_box: Vec<u8> = Vec::new();
+ cai_block.write_box(&mut mem_box)?;
- // Add assertions to CAI assertion store.
- let cas = claim.claim_assertion_store();
- for assertion in cas {
- Store::add_assertion_to_jumbf_store(&mut a_store, assertion)?;
- }
+ if mem_box.is_empty() {
+ Err(Error::JumbfCreationError)
+ } else {
+ Ok(mem_box)
+ }
+ }
- // Add the CAI assertion store to the CAI store.
- cai_store.add_box(Box::new(a_store));
+ fn build_manifest_box(claim: &Claim, min_reserve_size: usize) -> Result<CAIStore> {
+ // box label
+ let label = claim.label();
- // Add the Claim json
- let claim_cbor_bytes = claim.data()?;
- let c_cbor = JUMBFCBORContentBox::new(claim_cbor_bytes);
- cb.add_claim(Box::new(c_cbor));
- cai_store.add_box(Box::new(cb));
+ let mut cai_store = CAIStore::new(label, claim.update_manifest());
- // Create a signature and add placeholder data to the CAI store.
- let mut sigb = CAISignatureBox::new();
- let signed_data = match claim.signature_val().is_empty() {
- false => claim.signature_val().clone(), // existing claims have sig values
- true => self.sign_claim_placeholder(claim, min_reserve_size), // empty is the new sig to be replaced
- };
+ for manifest_box in claim.get_box_order() {
+ match *manifest_box {
+ ASSERTIONS => {
+ let mut a_store = CAIAssertionStore::new();
- let sigc = JUMBFCBORContentBox::new(signed_data);
- sigb.add_signature(Box::new(sigc));
- cai_store.add_box(Box::new(sigb));
-
- // add vc_store if needed
- if !claim.get_verifiable_credentials().is_empty() {
- // Create VC store.
- let mut vc_store = CAIVerifiableCredentialStore::new();
-
- // Add assertions to CAI assertion store.
- let vcs = claim.get_verifiable_credentials();
- for assertion_data in vcs {
- if let AssertionData::Json(j) = assertion_data {
- let id = Claim::vc_id(j)?;
- let mut json_data = CAIJSONAssertionBox::new(&id);
- json_data.add_json(j.as_bytes().to_vec());
- vc_store.add_credential(Box::new(json_data));
- } else {
- return Err(Error::BadParam("VC data must be JSON".to_string()));
+ // add assertions to CAI assertion store.
+ let cas = claim.claim_assertion_store();
+ for assertion in cas {
+ Store::add_assertion_to_jumbf_store(&mut a_store, assertion)?;
}
+
+ cai_store.add_box(Box::new(a_store)); // add the assertion store to the manifest
}
+ CLAIM => {
+ let mut cb = CAIClaimBox::new();
- // Add the CAI assertion store to the CAI store.
- cai_store.add_box(Box::new(vc_store));
- }
+ // Add the Claim json
+ let claim_cbor_bytes = claim.data()?;
+ let c_cbor = JUMBFCBORContentBox::new(claim_cbor_bytes);
+ cb.add_claim(Box::new(c_cbor));
- // Finally add the completed CAI store into the CAI block.
- cai_block.add_box(Box::new(cai_store));
+ cai_store.add_box(Box::new(cb)); // add claim to manifest
+ }
+ SIGNATURE => {
+ // create a signature and add placeholder data to the CAI store.
+ let mut sigb = CAISignatureBox::new();
+ let signed_data = match claim.signature_val().is_empty() {
+ false => claim.signature_val().clone(), // existing claims have sig values
+ true => Store::sign_claim_placeholder(claim, min_reserve_size), // empty is the new sig to be replaced
+ };
+
+ let sigc = JUMBFCBORContentBox::new(signed_data);
+ sigb.add_signature(Box::new(sigc));
+
+ cai_store.add_box(Box::new(sigb)); // add signature to manifest
+ }
+ CREDENTIALS => {
+ // add vc_store if needed
+ if !claim.get_verifiable_credentials().is_empty() {
+ let mut vc_store = CAIVerifiableCredentialStore::new();
+
+ // Add assertions to CAI assertion store.
+ let vcs = claim.get_verifiable_credentials_store();
+ for (uri, assertion_data) in vcs {
+ if let AssertionData::Json(j) = assertion_data {
+ let id = Claim::vc_id(j)?;
+ let mut json_data = CAIJSONAssertionBox::new(&id);
+ json_data.add_json(j.as_bytes().to_vec());
+
+ if let Some(salt) = uri.salt() {
+ json_data.set_salt(salt.clone())?;
+ }
+
+ vc_store.add_credential(Box::new(json_data));
+ } else {
+ return Err(Error::BadParam("VC data must be JSON".to_string()));
+ }
+ }
+ cai_store.add_box(Box::new(vc_store)); // add the CAI assertion store to manifest
+ }
+ }
+ _ => return Err(Error::ClaimInvalidContent),
+ }
}
- // Write it to memory.
- let mut mem_box: Vec<u8> = Vec::new();
- cai_block.write_box(&mut mem_box)?;
+ Ok(cai_store)
+ }
- if mem_box.is_empty() {
- Err(Error::JumbfCreationError)
- } else {
- Ok(mem_box)
+ // calculate the hash of the manifest JUMBF box
+ pub fn calc_manifest_box_hash(
+ claim: &Claim,
+ salt: Option<Vec<u8>>,
+ alg: &str,
+ ) -> Result<Vec<u8>> {
+ let mut hash_bytes = Vec::with_capacity(4096);
+
+ // build box
+ let mut cai_store = Store::build_manifest_box(claim, 0)?;
+
+ // add salt if requested
+ if let Some(salt) = salt {
+ cai_store.set_salt(salt)?;
}
+
+ // box content as Vec
+ cai_store.super_box().write_box_payload(&mut hash_bytes)?;
+
+ Ok(hash_by_alg(alg, &hash_bytes, None))
}
fn manifest_map<'a>(sb: &'a JUMBFSuperBox) -> Result<HashMap<String, ManifestInfo<'a>>> {
@@ -761,6 +816,9 @@ impl Store {
continue;
}
+ // remember the order of the boxes to insure the box hashes can be regenerated
+ let mut box_order: Vec<&str> = Vec::new();
+
// make sure there are not multiple claim boxes
let mut claim_box_cnt = 0;
for i in 0..cai_store_box.data_box_count() {
@@ -791,6 +849,23 @@ impl Store {
InvalidClaimError::C2paMultipleClaimBoxes,
));
}
+
+ match desc_box.label().as_ref() {
+ ASSERTIONS => box_order.push(ASSERTIONS),
+ CLAIM => box_order.push(CLAIM),
+ SIGNATURE => box_order.push(SIGNATURE),
+ CREDENTIALS => box_order.push(CREDENTIALS),
+ _ => {
+ let log_item =
+ log_item!("JUMBF", "unrecognized manifest box", "from_jumbf")
+ .error(Error::InvalidClaim(InvalidClaimError::ClaimBoxData))
+ .validation_status(validation_status::CLAIM_MULTIPLE);
+ validation_log.log(
+ log_item,
+ Some(Error::InvalidClaim(InvalidClaimError::ClaimBoxData)),
+ )?;
+ }
+ }
}
let is_update_manifest = cai_store_desc_box.uuid() == CAI_UPDATE_MANIFEST_UUID;
@@ -901,6 +976,9 @@ impl Store {
// set the type of manifest
claim.set_update_manifest(is_update_manifest);
+ // set order to process JUMBF boxes
+ claim.set_box_order(box_order);
+
// retrieve & set signature for each claim
claim.set_signature_val(sig_data.cbor().clone()); // load the stored signature
@@ -972,10 +1050,18 @@ impl Store {
let json_str = String::from_utf8(vc_json.json().to_vec())
.map_err(|_| InvalidClaimError::VerifiableCredentialStoreInvalid)?;
- claim.add_verifiable_credential(&json_str)?;
+ let salt = vc_desc_box.get_salt();
+
+ claim.put_verifiable_credential(&json_str, salt)?;
}
}
+ // save the hash of the loaded manifest for ingredient validation
+ store.manifest_box_hash_cache.insert(
+ claim.label().to_owned(),
+ Store::calc_manifest_box_hash(&claim, None, claim.alg())?,
+ );
+
// add claim to store
store.insert_restored_claim(cai_store_desc_box.label(), claim);
}
@@ -1019,7 +1105,18 @@ impl Store {
Some(a) => a,
None => ingredient.alg().to_owned(),
};
- if !verify_by_alg(&alg, &c2pa_manifest.hash(), &ingredient.data()?, None) {
+
+ // get the 1.1-1.2 box hash
+ let no_hash: Vec<u8> = Vec::new();
+ let box_hash = store
+ .manifest_box_hash_cache
+ .get(&label)
+ .unwrap_or(&no_hash);
+
+ // test for 1.1 hash then 1.0 version
+ if !vec_compare(&c2pa_manifest.hash(), box_hash)
+ && !verify_by_alg(&alg, &c2pa_manifest.hash(), &ingredient.data()?, None)
+ {
let log_item = log_item!(
&c2pa_manifest.url(),
"ingredient hash incorrect",
@@ -1448,7 +1545,7 @@ impl Store {
let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
let sig = self.sign_claim(pc, signer, signer.reserve_size())?;
- let sig_placeholder = self.sign_claim_placeholder(pc, signer.reserve_size());
+ let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
match self.finish_save_stream(jumbf_bytes, format, stream, sig, &sig_placeholder) {
Ok((s, m)) => {
@@ -1483,7 +1580,7 @@ impl Store {
let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
let sig = self.sign_claim(pc, signer, signer.reserve_size())?;
- let sig_placeholder = self.sign_claim_placeholder(pc, signer.reserve_size());
+ let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
// get correct output path for remote manifest
let output_path = match pc.remote_manifest() {
@@ -1540,7 +1637,7 @@ impl Store {
let sig = self
.sign_claim_async(pc, signer, signer.reserve_size())
.await?;
- let sig_placeholder = self.sign_claim_placeholder(pc, signer.reserve_size());
+ let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
// get correct output path for remote manifest
let output_path = match pc.remote_manifest() {
@@ -1596,7 +1693,7 @@ impl Store {
let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
let sig = remote_signer.sign_remote(&pc.data()?).await?;
- let sig_placeholder = self.sign_claim_placeholder(pc, remote_signer.reserve_size());
+ let sig_placeholder = Store::sign_claim_placeholder(pc, remote_signer.reserve_size());
// get correct output path for remote manifest
let output_path = match pc.remote_manifest() {
@@ -2198,10 +2295,11 @@ impl Store {
provenance_label: &str,
data: &[u8],
redactions: Option<Vec<String>>,
- ) -> Result<()> {
+ ) -> Result<Store> {
let mut report = OneShotStatusTracker::new();
let store = Store::from_jumbf(data, &mut report)?;
- claim.add_ingredient_data(provenance_label, store.claims, redactions)
+ claim.add_ingredient_data(provenance_label, store.claims.clone(), redactions)?;
+ Ok(store)
}
}
@@ -3086,6 +3184,15 @@ pub mod tests {
}
#[test]
+ fn test_legacy_ingredient_hash() {
+ // test 1.0 ingredient hash
+ let ap = fixture_path("legacy_ingredient_hash.jpg");
+ let mut report = DetailedStatusTracker::new();
+ let store = Store::load_from_asset(&ap, true, &mut report).expect("load_from_asset");
+ println!("store = {store}");
+ }
+
+ #[test]
#[cfg(all(feature = "file_io", feature = "bmff"))]
fn test_bmff_jumbf_generation() {
// test adding to actual image
diff --git a/sdk/tests/fixtures/legacy_ingredient_hash.jpg b/sdk/tests/fixtures/legacy_ingredient_hash.jpg
Binary files differ.