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

store.rs (199220B)


      1 // Copyright 2022 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 use std::{
     15     collections::HashMap,
     16     io::{Cursor, Read, Seek, SeekFrom},
     17 };
     18 #[cfg(feature = "file_io")]
     19 use std::{fs, path::Path};
     20 
     21 use async_generic::async_generic;
     22 use tracing::error;
     23 
     24 #[cfg(feature = "file_io")]
     25 use crate::jumbf_io::{
     26     get_file_extension, get_supported_file_extension, load_jumbf_from_file, object_locations,
     27     remove_jumbf_from_file, save_jumbf_to_file,
     28 };
     29 use crate::{
     30     assertion::{
     31         Assertion, AssertionBase, AssertionData, AssertionDecodeError, AssertionDecodeErrorCause,
     32     },
     33     assertions::{
     34         labels::{self, CLAIM},
     35         BmffHash, DataBox, DataHash, DataMap, ExclusionsMap, Ingredient, Relationship, SubsetMap,
     36     },
     37     asset_io::{
     38         CAIRead, CAIReadWrite, HashBlockObjectType, HashObjectPositions, RemoteRefEmbedType,
     39     },
     40     claim::{Claim, ClaimAssertion, ClaimAssetData, RemoteManifest},
     41     cose_sign::{cose_sign, cose_sign_async},
     42     cose_validator::{check_ocsp_status, verify_cose, verify_cose_async},
     43     error::{Error, Result},
     44     hash_utils::{hash_by_alg, vec_compare, verify_by_alg},
     45     jumbf::{
     46         self,
     47         boxes::*,
     48         labels::{to_absolute_uri, ASSERTIONS, CREDENTIALS, DATABOXES, SIGNATURE},
     49     },
     50     jumbf_io::{
     51         get_assetio_handler, is_bmff_format, load_jumbf_from_stream, object_locations_from_stream,
     52         save_jumbf_to_memory, save_jumbf_to_stream,
     53     },
     54     manifest_store_report::ManifestStoreReport,
     55     salt::DefaultSalt,
     56     settings::get_settings_value,
     57     status_tracker::{log_item, OneShotStatusTracker, StatusTracker},
     58     trust_handler::TrustHandlerConfig,
     59     utils::{
     60         hash_utils::{hash_sha256, HashRange},
     61         patch::patch_bytes,
     62     },
     63     validation_status, AsyncSigner, RemoteSigner, Signer,
     64 };
     65 
     66 const MANIFEST_STORE_EXT: &str = "c2pa"; // file extension for external manifests
     67 
     68 /// A `Store` maintains a list of `Claim` structs.
     69 ///
     70 /// Typically, this list of `Claim`s represents all of the claims in an asset.
     71 #[derive(Debug)]
     72 pub struct Store {
     73     claims_map: HashMap<String, usize>,
     74     manifest_box_hash_cache: HashMap<String, Vec<u8>>,
     75     claims: Vec<Claim>,
     76     label: String,
     77     provenance_path: Option<String>,
     78     trust_handler: Box<dyn TrustHandlerConfig>,
     79 }
     80 
     81 struct ManifestInfo<'a> {
     82     pub desc_box: &'a JUMBFDescriptionBox,
     83     pub sbox: &'a JUMBFSuperBox,
     84 }
     85 
     86 trait PushGetIndex {
     87     type Item;
     88     fn push_get_index(&mut self, item: Self::Item) -> usize;
     89 }
     90 
     91 impl<T> PushGetIndex for Vec<T> {
     92     type Item = T;
     93 
     94     fn push_get_index(&mut self, item: T) -> usize {
     95         let index = self.len();
     96         self.push(item);
     97         index
     98     }
     99 }
    100 
    101 impl Default for Store {
    102     fn default() -> Self {
    103         Self::new()
    104     }
    105 }
    106 
    107 impl Store {
    108     /// Create a new, empty claims store.
    109     pub fn new() -> Self {
    110         Self::new_with_label(MANIFEST_STORE_EXT)
    111     }
    112 
    113     /// Create a new, empty claims store with a custom label.
    114     ///
    115     /// In most cases, calling [`Store::new()`] is preferred.
    116     pub fn new_with_label(label: &str) -> Self {
    117         let mut store = Store {
    118             claims_map: HashMap::new(),
    119             manifest_box_hash_cache: HashMap::new(),
    120             claims: Vec::new(),
    121             label: label.to_string(),
    122             #[cfg(feature = "openssl")]
    123             trust_handler: Box::new(crate::openssl::OpenSSLTrustHandlerConfig::new()),
    124             #[cfg(all(not(feature = "openssl"), target_arch = "wasm32"))]
    125             trust_handler: Box::new(crate::wasm::WebTrustHandlerConfig::new()),
    126             #[cfg(all(not(feature = "openssl"), not(target_arch = "wasm32")))]
    127             trust_handler: Box::new(crate::trust_handler::TrustPassThrough::new()),
    128             provenance_path: None,
    129         };
    130 
    131         // load the trust handler settings, don't worry about status as these are checked during setting generation
    132         let _ = get_settings_value::<Option<String>>("trust.trust_anchors").map(|ta_opt| {
    133             if let Some(ta) = ta_opt {
    134                 let _v = store.add_trust(ta.as_bytes());
    135             }
    136         });
    137 
    138         let _ = get_settings_value::<Option<String>>("trust.private_anchors").map(|pa_opt| {
    139             if let Some(pa) = pa_opt {
    140                 let _v = store.add_private_trust_anchors(pa.as_bytes());
    141             }
    142         });
    143 
    144         let _ = get_settings_value::<Option<String>>("trust.trust_config").map(|tc_opt| {
    145             if let Some(tc) = tc_opt {
    146                 let _v = store.add_trust_config(tc.as_bytes());
    147             }
    148         });
    149 
    150         let _ = get_settings_value::<Option<String>>("trust.allowed_list").map(|al_opt| {
    151             if let Some(al) = al_opt {
    152                 let _v = store.add_trust_allowed_list(al.as_bytes());
    153             }
    154         });
    155 
    156         store
    157     }
    158 
    159     /// Return label for the store
    160     pub fn label(&self) -> &str {
    161         &self.label
    162     }
    163 
    164     /// Load set of trust anchors used for certificate validation. [u8] containing the
    165     /// trust anchors is passed in the trust_vec variable.
    166     pub fn add_trust(&mut self, trust_vec: &[u8]) -> Result<()> {
    167         let mut trust_reader = Cursor::new(trust_vec);
    168         self.trust_handler
    169             .load_trust_anchors_from_data(&mut trust_reader)
    170     }
    171 
    172     // Load set of private trust anchors used for certificate validation. [u8] to the
    173     /// private trust anchors is passed in the trust_vec variable.  This can be called multiple times
    174     /// if there are additional trust stores.
    175     pub fn add_private_trust_anchors(&mut self, trust_vec: &[u8]) -> Result<()> {
    176         let mut trust_reader = Cursor::new(trust_vec);
    177         self.trust_handler
    178             .append_private_trust_data(&mut trust_reader)
    179     }
    180 
    181     pub fn add_trust_config(&mut self, trust_vec: &[u8]) -> Result<()> {
    182         let mut trust_reader = Cursor::new(trust_vec);
    183         self.trust_handler.load_configuration(&mut trust_reader)
    184     }
    185 
    186     pub fn add_trust_allowed_list(&mut self, allowed_vec: &[u8]) -> Result<()> {
    187         let mut trust_reader = Cursor::new(allowed_vec);
    188         self.trust_handler.load_allowed_list(&mut trust_reader)
    189     }
    190 
    191     /// Clear all existing trust anchors
    192     pub fn clear_trust_anchors(&mut self) {
    193         self.trust_handler.clear();
    194     }
    195 
    196     fn trust_handler(&self) -> &dyn TrustHandlerConfig {
    197         self.trust_handler.as_ref()
    198     }
    199 
    200     /// Get the provenance if available.
    201     /// If loaded from an existing asset it will be provenance from the last claim.
    202     /// If a new claim is committed that will be the provenance claim
    203     pub fn provenance_path(&self) -> Option<String> {
    204         if self.provenance_path.is_none() {
    205             // if we have claims and no provenance, return last claim
    206             if let Some(claim) = self.claims.last() {
    207                 return Some(Claim::to_claim_uri(claim.label()));
    208             }
    209         }
    210         self.provenance_path.as_ref().cloned()
    211     }
    212 
    213     // set the path of the current provenance claim
    214     fn set_provenance_path(&mut self, claim_label: &str) {
    215         let path = Claim::to_claim_uri(claim_label);
    216         self.provenance_path = Some(path);
    217     }
    218 
    219     /// get the list of claims for this store
    220     pub const fn claims(&self) -> &Vec<Claim> {
    221         &self.claims
    222     }
    223 
    224     /// the JUMBF manifest box hash (spec 1.2)
    225     pub fn get_manifest_box_hash(&self, claim: &Claim) -> Vec<u8> {
    226         if let Some(bh) = self.manifest_box_hash_cache.get(claim.label()) {
    227             bh.clone()
    228         } else {
    229             Store::calc_manifest_box_hash(claim, None, claim.alg()).unwrap_or_default()
    230         }
    231     }
    232 
    233     /// Add a new Claim to this Store. The function
    234     /// will return the label of the claim.
    235     pub fn commit_claim(&mut self, mut claim: Claim) -> Result<String> {
    236         // make sure there is no pending unsigned claim
    237         if let Some(pc) = self.provenance_claim() {
    238             if pc.signature_val().is_empty() {
    239                 return Err(Error::ClaimUnsigned);
    240             }
    241         }
    242         // verify the claim is valid
    243         claim.build()?;
    244 
    245         // load the claim ingredients
    246         // parse first to make sure we can load them
    247         let mut ingredient_claims: Vec<Claim> = Vec::new();
    248         for (pc, claims) in claim.claim_ingredient_store() {
    249             let mut valid_pc = false;
    250 
    251             // expand for flat list insertion
    252             for ingredient_claim in claims {
    253                 // recreate claim from original bytes
    254                 let claim_clone = ingredient_claim.clone();
    255                 if pc == claim_clone.label() {
    256                     valid_pc = true;
    257                 }
    258                 ingredient_claims.push(claim_clone);
    259             }
    260             if !valid_pc {
    261                 return Err(Error::IngredientNotFound);
    262             }
    263         }
    264 
    265         // update the provenance path
    266         self.set_provenance_path(claim.label());
    267 
    268         let claim_label = claim.label().to_string();
    269 
    270         // insert ingredients if needed
    271         for ingredient_claim in ingredient_claims {
    272             let label = ingredient_claim.label().to_owned();
    273 
    274             if let std::collections::hash_map::Entry::Vacant(e) = self.claims_map.entry(label) {
    275                 let index = self.claims.push_get_index(ingredient_claim);
    276                 e.insert(index);
    277             }
    278         }
    279 
    280         // add claim to store after ingredients
    281         let index = self.claims.push_get_index(claim);
    282         self.claims_map.insert(claim_label.clone(), index);
    283 
    284         Ok(claim_label)
    285     }
    286 
    287     /// Add a new update manifest to this Store. The manifest label
    288     /// may be updated to reflect is position in the manifest Store
    289     /// if there are conflicting label names.  The function
    290     /// will return the label of the claim used
    291     pub fn commit_update_manifest(&mut self, mut claim: Claim) -> Result<String> {
    292         claim.set_update_manifest(true);
    293 
    294         // check for disallowed assertions
    295         if claim.has_assertion_type(labels::DATA_HASH)
    296             || claim.has_assertion_type(labels::ACTIONS)
    297             || claim.has_assertion_type(labels::BMFF_HASH)
    298         {
    299             return Err(Error::ClaimInvalidContent);
    300         }
    301 
    302         // must have exactly one ingredient
    303         let ingredient = match claim.get_assertion(Ingredient::LABEL, 0) {
    304             Some(i) => {
    305                 if claim.count_instances(Ingredient::LABEL) > 1 {
    306                     return Err(Error::ClaimInvalidContent);
    307                 } else {
    308                     i
    309                 }
    310             }
    311             None => return Err(Error::ClaimInvalidContent),
    312         };
    313 
    314         let ingredient_helper = Ingredient::from_assertion(ingredient)?;
    315 
    316         // must have a parent relationship
    317         if ingredient_helper.relationship != Relationship::ParentOf {
    318             return Err(Error::IngredientNotFound);
    319         }
    320 
    321         // make sure ingredient c2pa.manifest points to provenance claim
    322         if let Some(c2pa_manifest) = ingredient_helper.c2pa_manifest {
    323             // the manifest should refer to provenance claim
    324             if let Some(pc) = self.provenance_claim() {
    325                 if !c2pa_manifest.url().contains(pc.label()) {
    326                     return Err(Error::IngredientNotFound);
    327                 }
    328             } else {
    329                 return Err(Error::IngredientNotFound);
    330             }
    331         } else {
    332             return Err(Error::IngredientNotFound);
    333         }
    334 
    335         self.commit_claim(claim)
    336     }
    337 
    338     /// Get Claim by label
    339     // Returns Option<&Claim>
    340     pub fn get_claim(&self, label: &str) -> Option<&Claim> {
    341         #![allow(clippy::unwrap_used)] // since it's only in a debug_assert
    342         let index = self.claims_map.get(label)?;
    343         debug_assert!(self.claims.get(*index).unwrap().label() == label);
    344         self.claims.get(*index)
    345     }
    346 
    347     /// Get Claim by label
    348     // Returns Option<&Claim>
    349     pub fn get_claim_mut(&mut self, label: &str) -> Option<&mut Claim> {
    350         #![allow(clippy::unwrap_used)] // since it's only in a debug_assert
    351         let index = self.claims_map.get(label)?;
    352         debug_assert!(self.claims.get(*index).unwrap().label() == label);
    353         self.claims.get_mut(*index)
    354     }
    355 
    356     /// returns a Claim given a jumbf uri
    357     pub fn get_claim_from_uri(&self, uri: &str) -> Result<&Claim> {
    358         let claim_label = Store::manifest_label_from_path(uri);
    359         self.get_claim(&claim_label)
    360             .ok_or_else(|| Error::ClaimMissing {
    361                 label: claim_label.to_owned(),
    362             })
    363     }
    364 
    365     /// returns a ClaimAssertion given a jumbf uri, resolving to the right claim in the store
    366     pub fn get_claim_assertion_from_uri(&self, uri: &str) -> Result<&ClaimAssertion> {
    367         // first find the right claim and then look for the assertion there
    368         let claim = self.get_claim_from_uri(uri)?;
    369         let (label, instance) = Claim::assertion_label_from_link(uri);
    370         claim
    371             .get_claim_assertion(&label, instance)
    372             .ok_or_else(|| Error::ClaimMissing {
    373                 label: label.to_owned(),
    374             })
    375     }
    376 
    377     /// Returns an Assertion referenced by JUMBF URI.  The URI should be absolute and include
    378     /// the desired Claim in the path. If you need to specify the Claim for this URI use
    379     /// get_assertion_from_uri_and_claim.
    380     /// uri - The JUMBF URI for desired Assertion.
    381     pub fn get_assertion_from_uri(&self, uri: &str) -> Option<&Assertion> {
    382         let claim_label = Store::manifest_label_from_path(uri);
    383         let (assertion_label, instance) = Claim::assertion_label_from_link(uri);
    384 
    385         if let Some(claim) = self.get_claim(&claim_label) {
    386             claim.get_assertion(&assertion_label, instance)
    387         } else {
    388             None
    389         }
    390     }
    391 
    392     /// Returns an Assertion referenced by JUMBF URI. Only the Claim specified by target_claim_label
    393     /// will be searched.  The target_claim_label can be a Claim label or JUMBF URI.
    394     /// uri - The JUMBF URI for desired Assertion.
    395     /// target_claim_label - Label or URI of the Claim to search for the case when the URI is a relative path.
    396     pub fn get_assertion_from_uri_and_claim(
    397         &self,
    398         uri: &str,
    399         target_claim_label: &str,
    400     ) -> Option<&Assertion> {
    401         let (assertion_label, instance) = Claim::assertion_label_from_link(uri);
    402 
    403         let label = Store::manifest_label_from_path(target_claim_label);
    404 
    405         if let Some(claim) = self.get_claim(&label) {
    406             claim.get_assertion(&assertion_label, instance)
    407         } else {
    408             None
    409         }
    410     }
    411 
    412     /// Returns a DataBox referenced by JUMBF URI if it exists.
    413     ///
    414     /// Relative paths will use the provenance claim to resolve the DataBox.d
    415     pub fn get_data_box_from_uri_and_claim(
    416         &self,
    417         uri: &str,
    418         target_claim_label: &str,
    419     ) -> Option<&DataBox> {
    420         match jumbf::labels::manifest_label_from_uri(uri) {
    421             Some(label) => self.get_claim(&label), // use the manifest label from the thumbnail uri
    422             None => self.get_claim(target_claim_label), //  relative so use the target claim label
    423         }
    424         .and_then(|claim| {
    425             let uri = if target_claim_label != self.label() {
    426                 to_absolute_uri(target_claim_label, uri)
    427             } else {
    428                 uri.to_owned()
    429             };
    430             claim
    431                 .databoxes()
    432                 .iter()
    433                 .find(|(h, _d)| h.url() == uri)
    434                 .map(|(_sh, data_box)| data_box)
    435         })
    436     }
    437 
    438     // Returns placeholder that will be searched for and replaced
    439     // with actual signature data.
    440     fn sign_claim_placeholder(claim: &Claim, min_reserve_size: usize) -> Vec<u8> {
    441         let placeholder_str = format!("signature placeholder:{}", claim.label());
    442         let mut placeholder = hash_sha256(placeholder_str.as_bytes());
    443 
    444         use std::cmp::max;
    445         placeholder.resize(max(placeholder.len(), min_reserve_size), 0);
    446 
    447         placeholder
    448     }
    449 
    450     /// Return certificate chain for the provenance claim
    451     #[cfg(feature = "v1_api")]
    452     pub(crate) fn get_provenance_cert_chain(&self) -> Result<String> {
    453         let claim = self.provenance_claim().ok_or(Error::ProvenanceMissing)?;
    454 
    455         match claim.get_cert_chain() {
    456             Ok(chain) => String::from_utf8(chain).map_err(|_e| Error::CoseInvalidCert),
    457             Err(e) => Err(e),
    458         }
    459     }
    460 
    461     /// Return OCSP info if available
    462     // Currently only called from manifest_store behind a feature flag but this is allowable
    463     // anywhere so allow dead code here for future uses to compile
    464     #[allow(dead_code)]
    465     pub(crate) fn get_ocsp_status(&self) -> Option<String> {
    466         let claim = self
    467             .provenance_claim()
    468             .ok_or(Error::ProvenanceMissing)
    469             .ok()?;
    470 
    471         let sig = claim.signature_val();
    472         let data = claim.data().ok()?;
    473         let mut validation_log = OneShotStatusTracker::new();
    474 
    475         if let Ok(info) = check_ocsp_status(sig, &data, self.trust_handler(), &mut validation_log) {
    476             if let Some(revoked_at) = &info.revoked_at {
    477                 Some(format!(
    478                     "Certificate Status: Revoked, revoked at: {}",
    479                     revoked_at
    480                 ))
    481             } else {
    482                 Some(format!(
    483                     "Certificate Status: Good, next update: {}",
    484                     info.next_update
    485                 ))
    486             }
    487         } else {
    488             None
    489         }
    490     }
    491 
    492     /// Sign the claim and return signature.
    493     #[async_generic(async_signature(
    494         &self,
    495         claim: &Claim,
    496         signer: &dyn AsyncSigner,
    497         box_size: usize,
    498     ))]
    499     pub fn sign_claim(
    500         &self,
    501         claim: &Claim,
    502         signer: &dyn Signer,
    503         box_size: usize,
    504     ) -> Result<Vec<u8>> {
    505         let claim_bytes = claim.data()?;
    506 
    507         let result = if _sync {
    508             if signer.direct_cose_handling() {
    509                 // Let the signer do all the COSE processing and return the structured COSE data.
    510                 return signer.sign(&claim_bytes); // do not verify remote signers (we never did)
    511             } else {
    512                 cose_sign(signer, &claim_bytes, box_size)
    513             }
    514         } else {
    515             if signer.direct_cose_handling() {
    516                 // Let the signer do all the COSE processing and return the structured COSE data.
    517                 return signer.sign(claim_bytes.clone()).await; // do not verify remote signers (we never did)
    518             } else {
    519                 cose_sign_async(signer, &claim_bytes, box_size).await
    520             }
    521         };
    522         match result {
    523             Ok(sig) => {
    524                 // Sanity check: Ensure that this signature is valid.
    525                 if let Ok(verify_after_sign) =
    526                     get_settings_value::<bool>("verify.verify_after_sign")
    527                 {
    528                     if verify_after_sign {
    529                         let mut cose_log = OneShotStatusTracker::new();
    530 
    531                         let result = if _sync {
    532                             verify_cose(
    533                                 &sig,
    534                                 &claim_bytes,
    535                                 b"",
    536                                 false,
    537                                 self.trust_handler(),
    538                                 &mut cose_log,
    539                             )
    540                         } else {
    541                             verify_cose_async(
    542                                 sig.clone(),
    543                                 claim_bytes,
    544                                 b"".to_vec(),
    545                                 false,
    546                                 self.trust_handler(),
    547                                 &mut cose_log,
    548                             )
    549                             .await
    550                         };
    551                         if let Err(err) = result {
    552                             error!(
    553                                 "Signature that was just generated does not validate: {:#?}",
    554                                 err
    555                             );
    556                             return Err(err);
    557                         }
    558                     }
    559                 }
    560                 Ok(sig)
    561             }
    562             Err(e) => Err(e),
    563         }
    564     }
    565 
    566     /// return the current provenance claim label if available
    567     pub fn provenance_label(&self) -> Option<String> {
    568         self.provenance_path()
    569             .map(|provenance| Store::manifest_label_from_path(&provenance))
    570     }
    571 
    572     /// return the current provenance claim if available
    573     pub fn provenance_claim(&self) -> Option<&Claim> {
    574         match self.provenance_path() {
    575             Some(provenance) => {
    576                 let claim_label = Store::manifest_label_from_path(&provenance);
    577                 self.get_claim(&claim_label)
    578             }
    579             None => None,
    580         }
    581     }
    582 
    583     /// return the current provenance claim as mutable if available
    584     pub fn provenance_claim_mut(&mut self) -> Option<&mut Claim> {
    585         match self.provenance_path() {
    586             Some(provenance) => {
    587                 let claim_label = Store::manifest_label_from_path(&provenance);
    588                 self.get_claim_mut(&claim_label)
    589             }
    590             None => None,
    591         }
    592     }
    593 
    594     // add a restored claim
    595     fn insert_restored_claim(&mut self, label: String, claim: Claim) {
    596         let index = self.claims.push_get_index(claim);
    597         self.claims_map.insert(label, index);
    598     }
    599 
    600     fn add_assertion_to_jumbf_store(
    601         store: &mut CAIAssertionStore,
    602         claim_assertion: &ClaimAssertion,
    603     ) -> Result<()> {
    604         // Grab assertion data object.
    605         let d = claim_assertion.assertion().decode_data();
    606 
    607         match d {
    608             AssertionData::Json(_) => {
    609                 let mut json_data = CAIJSONAssertionBox::new(&claim_assertion.label());
    610                 json_data.add_json(claim_assertion.assertion().data().to_vec());
    611                 if let Some(salt) = claim_assertion.salt() {
    612                     json_data.set_salt(salt.clone())?;
    613                 }
    614                 store.add_assertion(Box::new(json_data));
    615             }
    616             AssertionData::Binary(_) => {
    617                 // TODO: Handle other binary box types if needed.
    618                 let mut data = JumbfEmbeddedFileBox::new(&claim_assertion.label());
    619                 data.add_data(
    620                     claim_assertion.assertion().data().to_vec(),
    621                     claim_assertion.assertion().mime_type(),
    622                     None,
    623                 );
    624                 if let Some(salt) = claim_assertion.salt() {
    625                     data.set_salt(salt.clone())?;
    626                 }
    627                 store.add_assertion(Box::new(data));
    628             }
    629             AssertionData::Cbor(_) => {
    630                 let mut cbor_data = CAICBORAssertionBox::new(&claim_assertion.label());
    631                 cbor_data.add_cbor(claim_assertion.assertion().data().to_vec());
    632                 if let Some(salt) = claim_assertion.salt() {
    633                     cbor_data.set_salt(salt.clone())?;
    634                 }
    635                 store.add_assertion(Box::new(cbor_data));
    636             }
    637             AssertionData::Uuid(s, _) => {
    638                 let mut uuid_data = CAIUUIDAssertionBox::new(&claim_assertion.label());
    639                 uuid_data.add_uuid(s, claim_assertion.assertion().data().to_vec())?;
    640                 if let Some(salt) = claim_assertion.salt() {
    641                     uuid_data.set_salt(salt.clone())?;
    642                 }
    643                 store.add_assertion(Box::new(uuid_data));
    644             }
    645         }
    646         Ok(())
    647     }
    648 
    649     // look for old style hashing to determine if this is a pre 1.0 claim
    650     fn is_old_assertion(alg: &str, data: &[u8], original_hash: &[u8]) -> bool {
    651         let old_hash = hash_by_alg(alg, data, None);
    652         vec_compare(&old_hash, original_hash)
    653     }
    654 
    655     fn get_assertion_from_jumbf_store(
    656         claim: &Claim,
    657         assertion_box: &JUMBFSuperBox,
    658         label: &str,
    659         check_for_legacy_assertion: bool,
    660     ) -> Result<ClaimAssertion> {
    661         let assertion_desc_box = assertion_box.desc_box();
    662 
    663         let (raw_label, instance) = Claim::assertion_label_from_link(label);
    664         let instance_label = Claim::label_with_instance(&raw_label, instance);
    665         let assertion_hashed_uri = claim
    666             .assertion_hashed_uri_from_label(&instance_label)
    667             .ok_or_else(|| {
    668                 Error::AssertionDecoding(AssertionDecodeError {
    669                     label: instance_label.to_string(),
    670                     version: None, // TODO: Plumb this through
    671                     content_type: "TO DO: Get content type".to_string(),
    672                     source: AssertionDecodeErrorCause::AssertionDataIncorrect,
    673                 })
    674             })?;
    675 
    676         let alg = match assertion_hashed_uri.alg() {
    677             Some(ref a) => a.clone(),
    678             None => claim.alg().to_string(),
    679         };
    680 
    681         // get salt value if set
    682         let salt = assertion_desc_box.get_salt();
    683 
    684         let result = match assertion_desc_box.uuid().as_ref() {
    685             CAI_JSON_ASSERTION_UUID => {
    686                 let json_box = assertion_box
    687                     .data_box_as_json_box(0)
    688                     .ok_or(Error::JumbfBoxNotFound)?;
    689                 let assertion = Assertion::from_data_json(&raw_label, json_box.json())?;
    690                 let hash = Claim::calc_assertion_box_hash(label, &assertion, salt.clone(), &alg)?;
    691                 Ok(ClaimAssertion::new(assertion, instance, &hash, &alg, salt))
    692             }
    693             CAI_EMBEDDED_FILE_UUID => {
    694                 let ef_box = assertion_box
    695                     .data_box_as_embedded_media_type_box(0)
    696                     .ok_or(Error::JumbfBoxNotFound)?;
    697                 let data_box = assertion_box
    698                     .data_box_as_embedded_file_content_box(1)
    699                     .ok_or(Error::JumbfBoxNotFound)?;
    700                 let media_type = ef_box.media_type();
    701                 let assertion =
    702                     Assertion::from_data_binary(&raw_label, &media_type, data_box.data());
    703                 let hash = Claim::calc_assertion_box_hash(label, &assertion, salt.clone(), &alg)?;
    704                 Ok(ClaimAssertion::new(assertion, instance, &hash, &alg, salt))
    705             }
    706             CAI_CBOR_ASSERTION_UUID => {
    707                 let cbor_box = assertion_box
    708                     .data_box_as_cbor_box(0)
    709                     .ok_or(Error::JumbfBoxNotFound)?;
    710                 let assertion = Assertion::from_data_cbor(&raw_label, cbor_box.cbor());
    711                 let hash = Claim::calc_assertion_box_hash(label, &assertion, salt.clone(), &alg)?;
    712                 Ok(ClaimAssertion::new(assertion, instance, &hash, &alg, salt))
    713             }
    714             CAI_UUID_ASSERTION_UUID => {
    715                 let uuid_box = assertion_box
    716                     .data_box_as_uuid_box(0)
    717                     .ok_or(Error::JumbfBoxNotFound)?;
    718                 let uuid_str = hex::encode(uuid_box.uuid());
    719                 let assertion = Assertion::from_data_uuid(&raw_label, &uuid_str, uuid_box.data());
    720 
    721                 let hash = Claim::calc_assertion_box_hash(label, &assertion, salt.clone(), &alg)?;
    722                 Ok(ClaimAssertion::new(assertion, instance, &hash, &alg, salt))
    723             }
    724             _ => Err(Error::JumbfCreationError),
    725         };
    726 
    727         if check_for_legacy_assertion {
    728             // make sure this is not pre 1.0 data
    729             match result {
    730                 Ok(r) => {
    731                     // look for old style hashing
    732                     if Store::is_old_assertion(
    733                         &alg,
    734                         r.assertion().data(),
    735                         &assertion_hashed_uri.hash(),
    736                     ) {
    737                         Err(Error::PrereleaseError)
    738                     } else {
    739                         Ok(r)
    740                     }
    741                 }
    742                 Err(e) => Err(e),
    743             }
    744         } else {
    745             result
    746         }
    747     }
    748 
    749     /// Convert this claims store to a JUMBF box.
    750     pub fn to_jumbf(&self, signer: &dyn Signer) -> Result<Vec<u8>> {
    751         self.to_jumbf_internal(signer.reserve_size())
    752     }
    753 
    754     /// Convert this claims store to a JUMBF box.
    755     pub fn to_jumbf_async(&self, signer: &dyn AsyncSigner) -> Result<Vec<u8>> {
    756         self.to_jumbf_internal(signer.reserve_size())
    757     }
    758 
    759     fn to_jumbf_internal(&self, min_reserve_size: usize) -> Result<Vec<u8>> {
    760         // Create the CAI block.
    761         let mut cai_block = Cai::new();
    762 
    763         // Add claims and assertions in this store to the JUMBF store.
    764         for claim in &self.claims {
    765             let cai_store = Store::build_manifest_box(claim, min_reserve_size)?;
    766 
    767             // add the completed CAI store into the CAI block.
    768             cai_block.add_box(Box::new(cai_store));
    769         }
    770 
    771         // Write it to memory.
    772         let mut mem_box: Vec<u8> = Vec::new();
    773         cai_block.write_box(&mut mem_box)?;
    774 
    775         if mem_box.is_empty() {
    776             Err(Error::JumbfCreationError)
    777         } else {
    778             Ok(mem_box)
    779         }
    780     }
    781 
    782     fn build_manifest_box(claim: &Claim, min_reserve_size: usize) -> Result<CAIStore> {
    783         // box label
    784         let label = claim.label();
    785 
    786         let mut cai_store = CAIStore::new(label, claim.update_manifest());
    787 
    788         for manifest_box in claim.get_box_order() {
    789             match *manifest_box {
    790                 ASSERTIONS => {
    791                     let mut a_store = CAIAssertionStore::new();
    792 
    793                     // add assertions to CAI assertion store.
    794                     let cas = claim.claim_assertion_store();
    795                     for assertion in cas {
    796                         Store::add_assertion_to_jumbf_store(&mut a_store, assertion)?;
    797                     }
    798 
    799                     cai_store.add_box(Box::new(a_store)); // add the assertion store to the manifest
    800                 }
    801                 CLAIM => {
    802                     let mut cb = CAIClaimBox::new();
    803 
    804                     // Add the Claim json
    805                     let claim_cbor_bytes = claim.data()?;
    806                     let c_cbor = JUMBFCBORContentBox::new(claim_cbor_bytes);
    807                     cb.add_claim(Box::new(c_cbor));
    808 
    809                     cai_store.add_box(Box::new(cb)); // add claim to manifest
    810                 }
    811                 SIGNATURE => {
    812                     // create a signature and add placeholder data to the CAI store.
    813                     let mut sigb = CAISignatureBox::new();
    814                     let signed_data = match claim.signature_val().is_empty() {
    815                         false => claim.signature_val().clone(), // existing claims have sig values
    816                         true => Store::sign_claim_placeholder(claim, min_reserve_size), /* empty is the new sig to be replaced */
    817                     };
    818 
    819                     let sigc = JUMBFCBORContentBox::new(signed_data);
    820                     sigb.add_signature(Box::new(sigc));
    821 
    822                     cai_store.add_box(Box::new(sigb)); // add signature to manifest
    823                 }
    824                 CREDENTIALS => {
    825                     // add vc_store if needed
    826                     if !claim.get_verifiable_credentials().is_empty() {
    827                         let mut vc_store = CAIVerifiableCredentialStore::new();
    828 
    829                         // Add assertions to CAI assertion store.
    830                         let vcs = claim.get_verifiable_credentials_store();
    831                         for (uri, assertion_data) in vcs {
    832                             if let AssertionData::Json(j) = assertion_data {
    833                                 let id = Claim::vc_id(j)?;
    834                                 let mut json_data = CAIJSONAssertionBox::new(&id);
    835                                 json_data.add_json(j.as_bytes().to_vec());
    836 
    837                                 if let Some(salt) = uri.salt() {
    838                                     json_data.set_salt(salt.clone())?;
    839                                 }
    840 
    841                                 vc_store.add_credential(Box::new(json_data));
    842                             } else {
    843                                 return Err(Error::BadParam("VC data must be JSON".to_string()));
    844                             }
    845                         }
    846                         cai_store.add_box(Box::new(vc_store)); // add the CAI assertion store to manifest
    847                     }
    848                 }
    849                 DATABOXES => {
    850                     // Add the data boxes
    851                     if !claim.databoxes().is_empty() {
    852                         let mut databoxes = CAIDataboxStore::new();
    853 
    854                         for (uri, db) in claim.databoxes() {
    855                             let db_cbor_bytes =
    856                                 serde_cbor::to_vec(db).map_err(|_err| Error::AssertionEncoding)?;
    857 
    858                             let (link, instance) = Claim::assertion_label_from_link(&uri.url());
    859                             let label = Claim::label_with_instance(&link, instance);
    860 
    861                             let mut db_cbor = CAICBORAssertionBox::new(&label);
    862                             db_cbor.add_cbor(db_cbor_bytes);
    863 
    864                             if let Some(salt) = uri.salt() {
    865                                 db_cbor.set_salt(salt.clone())?;
    866                             }
    867 
    868                             databoxes.add_databox(Box::new(db_cbor));
    869                         }
    870 
    871                         cai_store.add_box(Box::new(databoxes)); // add claim to manifest
    872                     }
    873                 }
    874                 _ => return Err(Error::ClaimInvalidContent),
    875             }
    876         }
    877 
    878         Ok(cai_store)
    879     }
    880 
    881     // calculate the hash of the manifest JUMBF box
    882     pub fn calc_manifest_box_hash(
    883         claim: &Claim,
    884         salt: Option<Vec<u8>>,
    885         alg: &str,
    886     ) -> Result<Vec<u8>> {
    887         let mut hash_bytes = Vec::with_capacity(4096);
    888 
    889         // build box
    890         let mut cai_store = Store::build_manifest_box(claim, 0)?;
    891 
    892         // add salt if requested
    893         if let Some(salt) = salt {
    894             cai_store.set_salt(salt)?;
    895         }
    896 
    897         // box content as Vec
    898         cai_store.super_box().write_box_payload(&mut hash_bytes)?;
    899 
    900         Ok(hash_by_alg(alg, &hash_bytes, None))
    901     }
    902 
    903     fn manifest_map<'a>(sb: &'a JUMBFSuperBox) -> Result<HashMap<String, ManifestInfo<'a>>> {
    904         let mut box_info: HashMap<String, ManifestInfo<'a>> = HashMap::new();
    905         for i in 0..sb.data_box_count() {
    906             let sbox = sb.data_box_as_superbox(i).ok_or(Error::JumbfBoxNotFound)?;
    907             let desc_box = sbox.desc_box();
    908 
    909             let label = desc_box.uuid();
    910 
    911             let mi = ManifestInfo { desc_box, sbox };
    912 
    913             box_info.insert(label, mi);
    914         }
    915 
    916         Ok(box_info)
    917     }
    918 
    919     // Compare two version labels
    920     // base_version_label - is the source label
    921     // desired_version_label - is the label to compare to the base
    922     // returns true if desired version is <= base version
    923     fn check_label_version(base_version_label: &str, desired_version_label: &str) -> bool {
    924         if let Some(desired_version) = labels::version(desired_version_label) {
    925             if let Some(base_version) = labels::version(base_version_label) {
    926                 if desired_version > base_version {
    927                     return false;
    928                 }
    929             }
    930         }
    931         true
    932     }
    933 
    934     pub fn from_jumbf(buffer: &[u8], validation_log: &mut impl StatusTracker) -> Result<Store> {
    935         if buffer.is_empty() {
    936             return Err(Error::JumbfNotFound);
    937         }
    938 
    939         let mut store = Store::new();
    940 
    941         // setup a cursor for reading the buffer...
    942         let mut buf_reader = Cursor::new(buffer);
    943 
    944         // this loads up all the boxes...
    945         let super_box = BoxReader::read_super_box(&mut buf_reader)?;
    946 
    947         // this loads up all the boxes...
    948         let cai_block = Cai::from(super_box);
    949 
    950         // check the CAI Block
    951         let desc_box = cai_block.desc_box();
    952         if desc_box.uuid() != CAI_BLOCK_UUID {
    953             let log_item = log_item!("JUMBF", "c2pa box not found", "from_jumbf")
    954                 .error(Error::InvalidClaim(InvalidClaimError::C2paBlockNotFound));
    955             validation_log.log(
    956                 log_item,
    957                 Some(Error::InvalidClaim(InvalidClaimError::C2paBlockNotFound)),
    958             )?;
    959 
    960             return Err(Error::InvalidClaim(InvalidClaimError::C2paBlockNotFound));
    961         }
    962 
    963         let num_stores = cai_block.data_box_count();
    964         for idx in 0..num_stores {
    965             let cai_store_box = cai_block
    966                 .data_box_as_superbox(idx)
    967                 .ok_or(Error::JumbfBoxNotFound)?;
    968             let cai_store_desc_box = cai_store_box.desc_box();
    969 
    970             // ignore unknown boxes per the spec
    971             if cai_store_desc_box.uuid() != CAI_UPDATE_MANIFEST_UUID
    972                 && cai_store_desc_box.uuid() != CAI_STORE_UUID
    973             {
    974                 continue;
    975             }
    976 
    977             // remember the order of the boxes to insure the box hashes can be regenerated
    978             let mut box_order: Vec<&str> = Vec::new();
    979 
    980             // make sure there are not multiple claim boxes
    981             let mut claim_box_cnt = 0;
    982             for i in 0..cai_store_box.data_box_count() {
    983                 let sbox = cai_store_box
    984                     .data_box_as_superbox(i)
    985                     .ok_or(Error::JumbfBoxNotFound)?;
    986                 let desc_box = sbox.desc_box();
    987 
    988                 if desc_box.uuid() == CAI_CLAIM_UUID {
    989                     claim_box_cnt += 1;
    990                 }
    991 
    992                 if claim_box_cnt > 1 {
    993                     let log_item =
    994                         log_item!("JUMBF", "c2pa multiple claim boxes found", "from_jumbf")
    995                             .error(Error::InvalidClaim(
    996                                 InvalidClaimError::C2paMultipleClaimBoxes,
    997                             ))
    998                             .validation_status(validation_status::CLAIM_MULTIPLE);
    999                     validation_log.log(
   1000                         log_item,
   1001                         Some(Error::InvalidClaim(
   1002                             InvalidClaimError::C2paMultipleClaimBoxes,
   1003                         )),
   1004                     )?;
   1005 
   1006                     return Err(Error::InvalidClaim(
   1007                         InvalidClaimError::C2paMultipleClaimBoxes,
   1008                     ));
   1009                 }
   1010 
   1011                 match desc_box.label().as_ref() {
   1012                     ASSERTIONS => box_order.push(ASSERTIONS),
   1013                     CLAIM => box_order.push(CLAIM),
   1014                     SIGNATURE => box_order.push(SIGNATURE),
   1015                     CREDENTIALS => box_order.push(CREDENTIALS),
   1016                     DATABOXES => box_order.push(DATABOXES),
   1017                     _ => {
   1018                         let log_item =
   1019                             log_item!("JUMBF", "unrecognized manifest box", "from_jumbf")
   1020                                 .error(Error::InvalidClaim(InvalidClaimError::ClaimBoxData))
   1021                                 .validation_status(validation_status::CLAIM_MULTIPLE);
   1022                         validation_log.log(
   1023                             log_item,
   1024                             Some(Error::InvalidClaim(InvalidClaimError::ClaimBoxData)),
   1025                         )?;
   1026                     }
   1027                 }
   1028             }
   1029 
   1030             let is_update_manifest = cai_store_desc_box.uuid() == CAI_UPDATE_MANIFEST_UUID;
   1031 
   1032             // get map of boxes in this manifest
   1033             let manifest_boxes = Store::manifest_map(cai_store_box)?;
   1034 
   1035             // retrieve the claim & validate
   1036             let claim_superbox = manifest_boxes
   1037                 .get(CAI_CLAIM_UUID)
   1038                 .ok_or(Error::InvalidClaim(
   1039                     InvalidClaimError::ClaimSuperboxNotFound,
   1040                 ))?
   1041                 .sbox;
   1042             let claim_desc_box = manifest_boxes
   1043                 .get(CAI_CLAIM_UUID)
   1044                 .ok_or(Error::InvalidClaim(
   1045                     InvalidClaimError::ClaimDescriptionBoxNotFound,
   1046                 ))?
   1047                 .desc_box;
   1048 
   1049             // check if version is supported
   1050             let claim_box_ver = claim_desc_box.label();
   1051             if !Self::check_label_version(Claim::build_version(), &claim_box_ver) {
   1052                 return Err(Error::InvalidClaim(InvalidClaimError::ClaimVersionTooNew));
   1053             }
   1054 
   1055             // check box contents
   1056             if claim_desc_box.uuid() == CAI_CLAIM_UUID {
   1057                 // must be have only one claim
   1058                 if claim_superbox.data_box_count() > 1 {
   1059                     return Err(Error::InvalidClaim(InvalidClaimError::DuplicateClaimBox {
   1060                         label: claim_desc_box.label(),
   1061                     }));
   1062                 }
   1063                 // better be, but just in case...
   1064 
   1065                 let cbor_box = match claim_superbox.data_box_as_cbor_box(0) {
   1066                     Some(c) => c,
   1067                     None => {
   1068                         // check for old claims for reporting
   1069                         match claim_superbox.data_box_as_json_box(0) {
   1070                             Some(_c) => {
   1071                                 let log_item =
   1072                                     log_item!("JUMBF", "error loading claim data", "from_jumbf")
   1073                                         .error(Error::PrereleaseError);
   1074                                 validation_log.log_silent(log_item);
   1075 
   1076                                 return Err(Error::PrereleaseError);
   1077                             }
   1078                             None => {
   1079                                 let log_item =
   1080                                     log_item!("JUMBF", "error loading claim data", "from_jumbf")
   1081                                         .error(Error::InvalidClaim(
   1082                                             InvalidClaimError::ClaimBoxData,
   1083                                         ));
   1084                                 validation_log.log_silent(log_item);
   1085                                 return Err(Error::InvalidClaim(InvalidClaimError::ClaimBoxData));
   1086                             }
   1087                         }
   1088                     }
   1089                 };
   1090 
   1091                 if cbor_box.box_uuid() != JUMBF_CBOR_UUID {
   1092                     return Err(Error::InvalidClaim(
   1093                         InvalidClaimError::ClaimDescriptionBoxInvalid,
   1094                     ));
   1095                 }
   1096             }
   1097 
   1098             // retrieve the signature
   1099             let sig_superbox = manifest_boxes
   1100                 .get(CAI_SIGNATURE_UUID)
   1101                 .ok_or(Error::InvalidClaim(
   1102                     InvalidClaimError::ClaimSignatureBoxNotFound,
   1103                 ))?
   1104                 .sbox;
   1105             let sig_desc_box = manifest_boxes
   1106                 .get(CAI_SIGNATURE_UUID)
   1107                 .ok_or(Error::InvalidClaim(
   1108                     InvalidClaimError::ClaimSignatureDescriptionBoxNotFound,
   1109                 ))?
   1110                 .desc_box;
   1111 
   1112             // check box contents
   1113             if sig_desc_box.uuid() == CAI_SIGNATURE_UUID {
   1114                 // better be, but just in case...
   1115                 let sig_box = sig_superbox
   1116                     .data_box_as_cbor_box(0)
   1117                     .ok_or(Error::JumbfBoxNotFound)?;
   1118                 if sig_box.box_uuid() != JUMBF_CBOR_UUID {
   1119                     return Err(Error::InvalidClaim(
   1120                         InvalidClaimError::ClaimSignatureDescriptionBoxInvalid,
   1121                     ));
   1122                 }
   1123             }
   1124             // save signature to be validated on load
   1125             let sig_data = sig_superbox
   1126                 .data_box_as_cbor_box(0)
   1127                 .ok_or(Error::JumbfBoxNotFound)?;
   1128 
   1129             // Create a new Claim object from jumbf data after validations
   1130             let cbor_box = claim_superbox
   1131                 .data_box_as_cbor_box(0)
   1132                 .ok_or(Error::JumbfBoxNotFound)?;
   1133             let mut claim = Claim::from_data(&cai_store_desc_box.label(), cbor_box.cbor())?;
   1134 
   1135             // set the  type of manifest
   1136             claim.set_update_manifest(is_update_manifest);
   1137 
   1138             // set order to process JUMBF boxes
   1139             claim.set_box_order(box_order);
   1140 
   1141             // retrieve & set signature for each claim
   1142             claim.set_signature_val(sig_data.cbor().clone()); // load the stored signature
   1143 
   1144             // retrieve the assertion store
   1145             let assertion_store_box = manifest_boxes
   1146                 .get(CAI_ASSERTION_STORE_UUID)
   1147                 .ok_or(Error::InvalidClaim(
   1148                     InvalidClaimError::AssertionStoreSuperboxNotFound,
   1149                 ))?
   1150                 .sbox;
   1151 
   1152             let num_assertions = assertion_store_box.data_box_count();
   1153 
   1154             // loop over all assertions...
   1155             let mut check_for_legacy_assertion = true;
   1156             for idx in 0..num_assertions {
   1157                 let assertion_box = assertion_store_box
   1158                     .data_box_as_superbox(idx)
   1159                     .ok_or(Error::JumbfBoxNotFound)?;
   1160                 let assertion_desc_box = assertion_box.desc_box();
   1161 
   1162                 // Add assertions to claim after validation
   1163                 let label = assertion_desc_box.label();
   1164                 match Store::get_assertion_from_jumbf_store(
   1165                     &claim,
   1166                     assertion_box,
   1167                     &label,
   1168                     check_for_legacy_assertion,
   1169                 ) {
   1170                     Ok(assertion) => {
   1171                         claim.put_assertion_store(assertion); // restore assertion data to claim
   1172                         check_for_legacy_assertion = false; // only need to check once
   1173                     }
   1174                     Err(e) => {
   1175                         // if this is an old manifest always return
   1176                         if std::mem::discriminant(&e)
   1177                             == std::mem::discriminant(&Error::PrereleaseError)
   1178                         {
   1179                             let log_item =
   1180                                 log_item!("JUMBF", "error loading assertion", "from_jumbf")
   1181                                     .error(e);
   1182                             validation_log.log_silent(log_item);
   1183                             return Err(Error::PrereleaseError);
   1184                         } else {
   1185                             let log_item =
   1186                                 log_item!("JUMBF", "error loading assertion", "from_jumbf")
   1187                                     .error(e);
   1188                             validation_log.log(log_item, None)?;
   1189                         }
   1190                     }
   1191                 }
   1192             }
   1193 
   1194             // load vc_store if available
   1195             if let Some(mi) = manifest_boxes.get(CAI_VERIFIABLE_CREDENTIALS_STORE_UUID) {
   1196                 let vc_store = mi.sbox;
   1197                 let num_vcs = vc_store.data_box_count();
   1198 
   1199                 for idx in 0..num_vcs {
   1200                     let vc_box = vc_store
   1201                         .data_box_as_superbox(idx)
   1202                         .ok_or(Error::JumbfBoxNotFound)?;
   1203                     let vc_json = vc_box
   1204                         .data_box_as_json_box(0)
   1205                         .ok_or(Error::JumbfBoxNotFound)?;
   1206                     let vc_desc_box = vc_box.desc_box();
   1207                     let _id = vc_desc_box.label();
   1208 
   1209                     let json_str = String::from_utf8(vc_json.json().to_vec())
   1210                         .map_err(|_| InvalidClaimError::VerifiableCredentialStoreInvalid)?;
   1211 
   1212                     let salt = vc_desc_box.get_salt();
   1213 
   1214                     claim.put_verifiable_credential(&json_str, salt)?;
   1215                 }
   1216             }
   1217 
   1218             // load databox store if available
   1219             if let Some(mi) = manifest_boxes.get(CAI_DATABOXES_STORE_UUID) {
   1220                 let databox_store = mi.sbox;
   1221                 let num_databoxes = databox_store.data_box_count();
   1222 
   1223                 for idx in 0..num_databoxes {
   1224                     let db_box = databox_store
   1225                         .data_box_as_superbox(idx)
   1226                         .ok_or(Error::JumbfBoxNotFound)?;
   1227                     let db_cbor = db_box
   1228                         .data_box_as_cbor_box(0)
   1229                         .ok_or(Error::JumbfBoxNotFound)?;
   1230                     let db_desc_box = db_box.desc_box();
   1231                     let label = db_desc_box.label();
   1232 
   1233                     let salt = db_desc_box.get_salt();
   1234 
   1235                     claim.put_data_box(&label, db_cbor.cbor(), salt)?;
   1236                 }
   1237             }
   1238 
   1239             // save the hash of the loaded manifest for ingredient validation
   1240             store.manifest_box_hash_cache.insert(
   1241                 claim.label().to_owned(),
   1242                 Store::calc_manifest_box_hash(&claim, None, claim.alg())?,
   1243             );
   1244 
   1245             // add claim to store
   1246             store.insert_restored_claim(cai_store_desc_box.label(), claim);
   1247         }
   1248 
   1249         Ok(store)
   1250     }
   1251 
   1252     // Get the store label from jumbf path
   1253     pub fn manifest_label_from_path(claim_path: &str) -> String {
   1254         if let Some(s) = jumbf::labels::manifest_label_from_uri(claim_path) {
   1255             s
   1256         } else {
   1257             claim_path.to_owned()
   1258         }
   1259     }
   1260 
   1261     // wake the ingredients and validate
   1262     fn ingredient_checks(
   1263         store: &Store,
   1264         claim: &Claim,
   1265         asset_data: &mut ClaimAssetData<'_>,
   1266         validation_log: &mut impl StatusTracker,
   1267     ) -> Result<()> {
   1268         let mut num_parent_ofs = 0;
   1269 
   1270         // walk the ingredients
   1271         for i in claim.ingredient_assertions() {
   1272             let ingredient_assertion = Ingredient::from_assertion(i)?;
   1273 
   1274             // is this an ingredient
   1275             if let Some(ref c2pa_manifest) = &ingredient_assertion.c2pa_manifest {
   1276                 let label = Store::manifest_label_from_path(&c2pa_manifest.url());
   1277 
   1278                 // check for parentOf relationships
   1279                 if ingredient_assertion.relationship == Relationship::ParentOf {
   1280                     num_parent_ofs += 1;
   1281                 }
   1282 
   1283                 if let Some(ingredient) = store.get_claim(&label) {
   1284                     let alg = match c2pa_manifest.alg() {
   1285                         Some(a) => a,
   1286                         None => ingredient.alg().to_owned(),
   1287                     };
   1288 
   1289                     // get the 1.1-1.2 box hash
   1290                     let no_hash: Vec<u8> = Vec::new();
   1291                     let box_hash = store
   1292                         .manifest_box_hash_cache
   1293                         .get(&label)
   1294                         .unwrap_or(&no_hash);
   1295 
   1296                     // test for 1.1 hash then 1.0 version
   1297                     if !vec_compare(&c2pa_manifest.hash(), box_hash)
   1298                         && !verify_by_alg(&alg, &c2pa_manifest.hash(), &ingredient.data()?, None)
   1299                     {
   1300                         let log_item = log_item!(
   1301                             &c2pa_manifest.url(),
   1302                             "ingredient hash incorrect",
   1303                             "ingredient_checks"
   1304                         )
   1305                         .error(Error::HashMismatch(
   1306                             "ingredient hash does not match found ingredient".to_string(),
   1307                         ))
   1308                         .validation_status(validation_status::INGREDIENT_HASHEDURI_MISMATCH);
   1309                         validation_log.log(
   1310                             log_item,
   1311                             Some(Error::HashMismatch(
   1312                                 "ingredient hash does not match found ingredient".to_string(),
   1313                             )),
   1314                         )?;
   1315                     }
   1316 
   1317                     // make sure
   1318                     // verify the ingredient claim
   1319                     Claim::verify_claim(
   1320                         ingredient,
   1321                         asset_data,
   1322                         false,
   1323                         store.trust_handler(),
   1324                         validation_log,
   1325                     )?;
   1326                 } else {
   1327                     let log_item = log_item!(
   1328                         &c2pa_manifest.url(),
   1329                         "ingredient not found",
   1330                         "ingredient_checks"
   1331                     )
   1332                     .error(Error::ClaimVerification(format!(
   1333                         "ingredient: {label} is missing"
   1334                     )))
   1335                     .validation_status(validation_status::CLAIM_MISSING);
   1336                     validation_log.log(
   1337                         log_item,
   1338                         Some(Error::ClaimVerification(format!(
   1339                             "ingredient: {label} is missing"
   1340                         ))),
   1341                     )?;
   1342                 }
   1343             }
   1344         }
   1345 
   1346         // check ingredient rules
   1347         if claim.update_manifest() {
   1348             if !(num_parent_ofs == 1 && claim.ingredient_assertions().len() == 1) {
   1349                 let log_item = log_item!(
   1350                     &claim.uri(),
   1351                     "update manifest must have one parent",
   1352                     "ingredient_checks"
   1353                 )
   1354                 .error(Error::ClaimVerification(
   1355                     "update manifest must have one parent".to_string(),
   1356                 ))
   1357                 .validation_status(validation_status::MANIFEST_UPDATE_WRONG_PARENTS);
   1358                 validation_log.log(
   1359                     log_item,
   1360                     Some(Error::ClaimVerification(
   1361                         "update manifest must have one parent".to_string(),
   1362                     )),
   1363                 )?;
   1364             }
   1365         } else if num_parent_ofs > 1 {
   1366             let log_item = log_item!(
   1367                 &claim.uri(),
   1368                 "too many ingredient parents",
   1369                 "ingredient_checks"
   1370             )
   1371             .error(Error::ClaimVerification(
   1372                 "ingredient has more than one parent".to_string(),
   1373             ))
   1374             .validation_status(validation_status::MANIFEST_MULTIPLE_PARENTS);
   1375             validation_log.log(
   1376                 log_item,
   1377                 Some(Error::ClaimVerification(
   1378                     "ingredient has more than one parent".to_string(),
   1379                 )),
   1380             )?;
   1381         }
   1382 
   1383         Ok(())
   1384     }
   1385 
   1386     // wake the ingredients and validate
   1387     async fn ingredient_checks_async(
   1388         store: &Store,
   1389         claim: &Claim,
   1390         asset_data: &mut ClaimAssetData<'_>,
   1391         validation_log: &mut impl StatusTracker,
   1392     ) -> Result<()> {
   1393         // walk the ingredients
   1394         for i in claim.ingredient_assertions() {
   1395             let ingredient_assertion = Ingredient::from_assertion(i)?;
   1396 
   1397             // is this an ingredient
   1398             if let Some(ref c2pa_manifest) = &ingredient_assertion.c2pa_manifest {
   1399                 let label = Store::manifest_label_from_path(&c2pa_manifest.url());
   1400 
   1401                 if let Some(ingredient) = store.get_claim(&label) {
   1402                     let alg = match c2pa_manifest.alg() {
   1403                         Some(a) => a,
   1404                         None => ingredient.alg().to_owned(),
   1405                     };
   1406 
   1407                     // get the 1.1-1.2 box hash
   1408                     let no_hash: Vec<u8> = Vec::new();
   1409                     let box_hash = store
   1410                         .manifest_box_hash_cache
   1411                         .get(&label)
   1412                         .unwrap_or(&no_hash);
   1413 
   1414                     // test for 1.1 hash then 1.0 version
   1415                     if !vec_compare(&c2pa_manifest.hash(), box_hash)
   1416                         && !verify_by_alg(&alg, &c2pa_manifest.hash(), &ingredient.data()?, None)
   1417                     {
   1418                         let log_item = log_item!(
   1419                             &c2pa_manifest.url(),
   1420                             "ingredient hash incorrect",
   1421                             "ingredient_checks_async"
   1422                         )
   1423                         .error(Error::HashMismatch(
   1424                             "ingredient hash does not match found ingredient".to_string(),
   1425                         ))
   1426                         .validation_status(validation_status::INGREDIENT_HASHEDURI_MISMATCH);
   1427                         validation_log.log(
   1428                             log_item,
   1429                             Some(Error::HashMismatch(
   1430                                 "ingredient hash does not match found ingredient".to_string(),
   1431                             )),
   1432                         )?;
   1433                     }
   1434                     // verify the ingredient claim
   1435                     Claim::verify_claim_async(
   1436                         ingredient,
   1437                         asset_data,
   1438                         false,
   1439                         store.trust_handler(),
   1440                         validation_log,
   1441                     )
   1442                     .await?;
   1443                 } else {
   1444                     let log_item = log_item!(
   1445                         &c2pa_manifest.url(),
   1446                         "ingredient not found",
   1447                         "ingredient_checks_async"
   1448                     )
   1449                     .error(Error::ClaimVerification(format!(
   1450                         "ingredient: {label} is missing"
   1451                     )))
   1452                     .validation_status(validation_status::CLAIM_MISSING);
   1453                     validation_log.log(
   1454                         log_item,
   1455                         Some(Error::ClaimVerification(format!(
   1456                             "ingredient: {label} is missing"
   1457                         ))),
   1458                     )?;
   1459                 }
   1460             }
   1461         }
   1462 
   1463         Ok(())
   1464     }
   1465 
   1466     /// Verify Store
   1467     /// store: Store to validate
   1468     /// xmp_str: String containing entire XMP block of the asset
   1469     /// asset_bytes: bytes of the asset to be verified
   1470     /// validation_log: If present all found errors are logged and returned, other wise first error causes exit and is returned
   1471     pub async fn verify_store_async(
   1472         store: &Store,
   1473         asset_data: &mut ClaimAssetData<'_>,
   1474         validation_log: &mut impl StatusTracker,
   1475     ) -> Result<()> {
   1476         let claim = match store.provenance_claim() {
   1477             Some(c) => c,
   1478             None => {
   1479                 let log_item =
   1480                     log_item!("Unknown", "could not find active manifest", "verify_store")
   1481                         .error(Error::ProvenanceMissing)
   1482                         .validation_status(validation_status::CLAIM_MISSING);
   1483                 validation_log.log(log_item, Some(Error::ProvenanceMissing))?;
   1484 
   1485                 return Err(Error::ProvenanceMissing);
   1486             }
   1487         };
   1488 
   1489         // verify the provenance claim
   1490         Claim::verify_claim_async(
   1491             claim,
   1492             asset_data,
   1493             true,
   1494             store.trust_handler(),
   1495             validation_log,
   1496         )
   1497         .await?;
   1498 
   1499         Store::ingredient_checks_async(store, claim, asset_data, validation_log).await?;
   1500 
   1501         Ok(())
   1502     }
   1503 
   1504     /// Verify Store
   1505     /// store: Store to validate
   1506     /// xmp_str: String containing entire XMP block of the asset
   1507     /// asset_bytes: bytes of the asset to be verified
   1508     /// validation_log: If present all found errors are logged and returned, other wise first error causes exit and is returned
   1509     pub fn verify_store(
   1510         store: &Store,
   1511         asset_data: &mut ClaimAssetData<'_>,
   1512         validation_log: &mut impl StatusTracker,
   1513     ) -> Result<()> {
   1514         let claim = match store.provenance_claim() {
   1515             Some(c) => c,
   1516             None => {
   1517                 let log_item =
   1518                     log_item!("Unknown", "could not find active manifest", "verify_store")
   1519                         .error(Error::ProvenanceMissing)
   1520                         .validation_status(validation_status::CLAIM_MISSING);
   1521                 validation_log.log(log_item, Some(Error::ProvenanceMissing))?;
   1522 
   1523                 return Err(Error::ProvenanceMissing);
   1524             }
   1525         };
   1526 
   1527         // verify the provenance claim
   1528         Claim::verify_claim(
   1529             claim,
   1530             asset_data,
   1531             true,
   1532             store.trust_handler(),
   1533             validation_log,
   1534         )?;
   1535 
   1536         Store::ingredient_checks(store, claim, asset_data, validation_log)?;
   1537 
   1538         Ok(())
   1539     }
   1540 
   1541     // generate a list of AssetHashes based on the location of objects in the file
   1542     #[cfg(feature = "file_io")]
   1543     fn generate_data_hashes(
   1544         asset_path: &Path,
   1545         alg: &str,
   1546         block_locations: &mut Vec<HashObjectPositions>,
   1547         calc_hashes: bool,
   1548     ) -> Result<Vec<DataHash>> {
   1549         let mut file = std::fs::File::open(asset_path)?;
   1550         Self::generate_data_hashes_for_stream(&mut file, alg, block_locations, calc_hashes)
   1551     }
   1552 
   1553     // generate a list of AssetHashes based on the location of objects in the stream
   1554     fn generate_data_hashes_for_stream<R>(
   1555         stream: &mut R,
   1556         alg: &str,
   1557         block_locations: &mut Vec<HashObjectPositions>,
   1558         calc_hashes: bool,
   1559     ) -> Result<Vec<DataHash>>
   1560     where
   1561         R: Read + Seek + ?Sized,
   1562     {
   1563         if block_locations.is_empty() {
   1564             let out: Vec<DataHash> = vec![];
   1565             return Ok(out);
   1566         }
   1567 
   1568         let stream_len = stream.seek(SeekFrom::End(0))?;
   1569         stream.rewind()?;
   1570 
   1571         let mut hashes: Vec<DataHash> = Vec::new();
   1572 
   1573         // sort blocks by offset
   1574         block_locations.sort_by(|a, b| a.offset.cmp(&b.offset));
   1575 
   1576         // generate default data hash that excludes jumbf block
   1577         // find the first jumbf block (ours are always in order)
   1578         // find the first block after the jumbf blocks
   1579         let mut block_start: usize = 0;
   1580         let mut block_end: usize = 0;
   1581         let mut found_jumbf = false;
   1582         for item in block_locations {
   1583             // find start of jumbf
   1584             if !found_jumbf && item.htype == HashBlockObjectType::Cai {
   1585                 block_start = item.offset;
   1586                 found_jumbf = true;
   1587             }
   1588 
   1589             // find start of block after jumbf blocks
   1590             if found_jumbf && item.htype == HashBlockObjectType::Cai {
   1591                 block_end = item.offset + item.length;
   1592             }
   1593         }
   1594 
   1595         if found_jumbf {
   1596             // add exclusion hash for bytes before and after jumbf
   1597             let mut dh = DataHash::new("jumbf manifest", alg);
   1598             if block_end > block_start {
   1599                 dh.add_exclusion(HashRange::new(block_start, block_end - block_start));
   1600             }
   1601 
   1602             if calc_hashes {
   1603                 // this check is only valid on the final sized asset
   1604                 if block_end as u64 > stream_len {
   1605                     return Err(Error::BadParam(
   1606                         "data hash exclusions out of range".to_string(),
   1607                     ));
   1608                 }
   1609 
   1610                 dh.gen_hash_from_stream(stream)?;
   1611             } else {
   1612                 match alg {
   1613                     "sha256" => dh.set_hash([0u8; 32].to_vec()),
   1614                     "sha384" => dh.set_hash([0u8; 48].to_vec()),
   1615                     "sha512" => dh.set_hash([0u8; 64].to_vec()),
   1616                     _ => return Err(Error::UnsupportedType),
   1617                 }
   1618             }
   1619             hashes.push(dh);
   1620         }
   1621 
   1622         Ok(hashes)
   1623     }
   1624 
   1625     fn generate_bmff_data_hashes(
   1626         asset_stream: &mut dyn CAIRead,
   1627         alg: &str,
   1628         calc_hashes: bool,
   1629     ) -> Result<Vec<BmffHash>> {
   1630         use serde_bytes::ByteBuf;
   1631 
   1632         // The spec has mandatory BMFF exclusion ranges for certain atoms.
   1633         // The function makes sure those are included.
   1634 
   1635         let mut hashes: Vec<BmffHash> = Vec::new();
   1636 
   1637         let mut dh = BmffHash::new("jumbf manifest", alg, None);
   1638         let exclusions = dh.exclusions_mut();
   1639 
   1640         // jumbf exclusion
   1641         let mut uuid = ExclusionsMap::new("/uuid".to_owned());
   1642         let data = DataMap {
   1643             offset: 8,
   1644             value: vec![
   1645                 216, 254, 195, 214, 27, 14, 72, 60, 146, 151, 88, 40, 135, 126, 196, 129,
   1646             ], // C2PA identifier
   1647         };
   1648         let data_vec = vec![data];
   1649         uuid.data = Some(data_vec);
   1650         exclusions.push(uuid);
   1651 
   1652         // ftyp exclusion
   1653         let ftyp = ExclusionsMap::new("/ftyp".to_owned());
   1654         exclusions.push(ftyp);
   1655 
   1656         // meta/iloc exclusion
   1657         let iloc = ExclusionsMap::new("/meta/iloc".to_owned());
   1658         exclusions.push(iloc);
   1659 
   1660         // /mfra/tfra exclusion
   1661         let tfra = ExclusionsMap::new("/mfra/tfra".to_owned());
   1662         exclusions.push(tfra);
   1663 
   1664         // /moov/trak/mdia/minf/stbl/stco exclusion
   1665         let mut stco = ExclusionsMap::new("/moov/trak/mdia/minf/stbl/stco".to_owned());
   1666         let subset_stco = SubsetMap {
   1667             offset: 16,
   1668             length: 0,
   1669         };
   1670         let subset_stco_vec = vec![subset_stco];
   1671         stco.subset = Some(subset_stco_vec);
   1672         exclusions.push(stco);
   1673 
   1674         // /moov/trak/mdia/minf/stbl/co64 exclusion
   1675         let mut co64 = ExclusionsMap::new("/moov/trak/mdia/minf/stbl/co64".to_owned());
   1676         let subset_co64 = SubsetMap {
   1677             offset: 16,
   1678             length: 0,
   1679         };
   1680         let subset_co64_vec = vec![subset_co64];
   1681         co64.subset = Some(subset_co64_vec);
   1682         exclusions.push(co64);
   1683 
   1684         // /moof/traf/tfhd exclusion
   1685         let mut tfhd = ExclusionsMap::new("/moof/traf/tfhd".to_owned());
   1686         let subset_tfhd = SubsetMap {
   1687             offset: 16,
   1688             length: 8,
   1689         };
   1690         let subset_tfhd_vec = vec![subset_tfhd];
   1691         tfhd.subset = Some(subset_tfhd_vec);
   1692         tfhd.flags = Some(ByteBuf::from([1, 0, 0]));
   1693         exclusions.push(tfhd);
   1694 
   1695         // /moof/traf/trun exclusion
   1696         let mut trun = ExclusionsMap::new("/moof/traf/trun".to_owned());
   1697         let subset_trun = SubsetMap {
   1698             offset: 16,
   1699             length: 4,
   1700         };
   1701         let subset_trun_vec = vec![subset_trun];
   1702         trun.subset = Some(subset_trun_vec);
   1703         trun.flags = Some(ByteBuf::from([1, 0, 0]));
   1704         exclusions.push(trun);
   1705 
   1706         // V2 exclusions
   1707         /*  Enable this when we support Merkle trees and fragmented MP4
   1708         // /mdat exclusion
   1709         let mut mdat = ExclusionsMap::new("/mdat".to_owned());
   1710         let subset_mdat = SubsetMap {
   1711             offset: 16,
   1712             length: 0,
   1713         };
   1714         let subset_mdat_vec = vec![subset_mdat];
   1715         mdat.subset = Some(subset_mdat_vec);
   1716         exclusions.push(mdat);
   1717         */
   1718 
   1719         if calc_hashes {
   1720             dh.gen_hash_from_stream(asset_stream)?;
   1721         } else {
   1722             match alg {
   1723                 "sha256" => dh.set_hash([0u8; 32].to_vec()),
   1724                 "sha384" => dh.set_hash([0u8; 48].to_vec()),
   1725                 "sha512" => dh.set_hash([0u8; 64].to_vec()),
   1726                 _ => return Err(Error::UnsupportedType),
   1727             }
   1728         }
   1729         hashes.push(dh);
   1730 
   1731         Ok(hashes)
   1732     }
   1733 
   1734     // move or copy data from source to dest
   1735     #[cfg(feature = "file_io")]
   1736     fn move_or_copy(source: &Path, dest: &Path) -> Result<()> {
   1737         // copy temp file to asset
   1738         std::fs::rename(source, dest)
   1739             // if rename fails, try to copy in case we are on different volumes or output does not exist
   1740             .or_else(|_| std::fs::copy(source, dest).and(Ok(())))
   1741             .map_err(Error::IoError)
   1742     }
   1743 
   1744     // copy output and possibly the external manifest to final destination
   1745     #[cfg(feature = "file_io")]
   1746     fn copy_c2pa_to_output(source: &Path, dest: &Path, remote_type: RemoteManifest) -> Result<()> {
   1747         match remote_type {
   1748             RemoteManifest::NoRemote => Store::move_or_copy(source, dest)?,
   1749             RemoteManifest::SideCar
   1750             | RemoteManifest::Remote(_)
   1751             | RemoteManifest::EmbedWithRemote(_) => {
   1752                 // make correct path names
   1753                 let source_asset = source;
   1754                 let source_cai = source_asset.with_extension(MANIFEST_STORE_EXT);
   1755                 let dest_cai = dest.with_extension(MANIFEST_STORE_EXT);
   1756 
   1757                 Store::move_or_copy(&source_cai, &dest_cai)?; // copy manifest
   1758                 Store::move_or_copy(source_asset, dest)?; // copy asset
   1759             }
   1760         }
   1761         Ok(())
   1762     }
   1763 
   1764     /// This function is used to pre-generate a manifest with place holders for the final
   1765     /// DataHash and Manifest Signature.  The DataHash will reserve space for at least 10
   1766     /// Exclusion ranges.  The Signature box reserved size is based on the size required by
   1767     /// the Signer you plan to use.  This function is not needed when using Box Hash. This function is used
   1768     /// in conjunction with `get_data_hashed_embeddable_manifest`.  The manifest returned
   1769     /// from `get_data_hashed_embeddable_manifest` will have a size that matches this function.
   1770     pub fn get_data_hashed_manifest_placeholder(
   1771         &mut self,
   1772         reserve_size: usize,
   1773         format: &str,
   1774     ) -> Result<Vec<u8>> {
   1775         let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   1776 
   1777         // if user did not supply a hash
   1778         if pc.hash_assertions().is_empty() {
   1779             // create placeholder DataHash large enough for 10 Exclusions
   1780             let mut ph = DataHash::new("jumbf manifest", pc.alg());
   1781             for _ in 0..10 {
   1782                 ph.add_exclusion(HashRange::new(0, 2));
   1783             }
   1784             let data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10];
   1785             let mut stream = Cursor::new(data);
   1786             ph.gen_hash_from_stream(&mut stream)?;
   1787 
   1788             pc.add_assertion_with_salt(&ph, &DefaultSalt::default())?;
   1789         }
   1790 
   1791         let jumbf_bytes = self.to_jumbf_internal(reserve_size)?;
   1792 
   1793         let composed = Self::get_composed_manifest(&jumbf_bytes, format)?;
   1794 
   1795         Ok(composed)
   1796     }
   1797 
   1798     fn prep_embeddable_store(
   1799         &mut self,
   1800         reserve_size: usize,
   1801         dh: &DataHash,
   1802         asset_reader: Option<&mut dyn CAIRead>,
   1803     ) -> Result<Vec<u8>> {
   1804         let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   1805 
   1806         // make sure there are data hashes present before generating
   1807         if pc.hash_assertions().is_empty() {
   1808             return Err(Error::BadParam(
   1809                 "Claim must have hash binding assertion".to_string(),
   1810             ));
   1811         }
   1812 
   1813         // don't allow BMFF assertions to be present
   1814         if !pc.bmff_hash_assertions().is_empty() {
   1815             return Err(Error::BadParam(
   1816                 "BMFF assertions not supported in embeddable manifests".to_string(),
   1817             ));
   1818         }
   1819 
   1820         let mut adjusted_dh = DataHash::new("jumbf manifest", pc.alg());
   1821         adjusted_dh.exclusions.clone_from(&dh.exclusions);
   1822         adjusted_dh.hash.clone_from(&dh.hash);
   1823 
   1824         if let Some(reader) = asset_reader {
   1825             // calc hashes
   1826             adjusted_dh.gen_hash_from_stream(reader)?;
   1827         }
   1828 
   1829         // update the placeholder hash
   1830         pc.update_data_hash(adjusted_dh)?;
   1831 
   1832         self.to_jumbf_internal(reserve_size)
   1833     }
   1834 
   1835     fn finish_embeddable_store(
   1836         &mut self,
   1837         sig: &[u8],
   1838         sig_placeholder: &[u8],
   1839         jumbf_bytes: &mut Vec<u8>,
   1840         format: &str,
   1841     ) -> Result<Vec<u8>> {
   1842         if sig_placeholder.len() != sig.len() {
   1843             return Err(Error::CoseSigboxTooSmall);
   1844         }
   1845 
   1846         patch_bytes(jumbf_bytes, sig_placeholder, sig).map_err(|_| Error::JumbfCreationError)?;
   1847 
   1848         Self::get_composed_manifest(jumbf_bytes, format)
   1849     }
   1850 
   1851     /// Returns a finalized, signed manifest.  The manifest are only supported
   1852     /// for cases when the client has provided a data hash content hash binding.  Note,
   1853     /// this function will not work for cases like BMFF where the position
   1854     /// of the content is also encoded.  This function is not compatible with
   1855     /// BMFF hash binding.  If a BMFF data hash or box hash is detected that is
   1856     /// an error.  The DataHash placeholder assertion will be  adjusted to the contain
   1857     /// the correct values.  If the asset_reader value is supplied it will also perform
   1858     /// the hash calculations, otherwise the function uses the caller supplied values.
   1859     /// It is an error if `get_data_hashed_manifest_placeholder` was not called first
   1860     /// as this call inserts the DataHash placeholder assertion to reserve space for the
   1861     /// actual hash values not required when using BoxHashes.
   1862     pub fn get_data_hashed_embeddable_manifest(
   1863         &mut self,
   1864         dh: &DataHash,
   1865         signer: &dyn Signer,
   1866         format: &str,
   1867         asset_reader: Option<&mut dyn CAIRead>,
   1868     ) -> Result<Vec<u8>> {
   1869         let mut jumbf_bytes =
   1870             self.prep_embeddable_store(signer.reserve_size(), dh, asset_reader)?;
   1871 
   1872         // sign contents
   1873         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   1874         let sig = self.sign_claim(pc, signer, signer.reserve_size())?;
   1875 
   1876         let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
   1877 
   1878         self.finish_embeddable_store(&sig, &sig_placeholder, &mut jumbf_bytes, format)
   1879     }
   1880 
   1881     /// Returns a finalized, signed manifest.  The manifest are only supported
   1882     /// for cases when the client has provided a data hash content hash binding.  Note,
   1883     /// this function will not work for cases like BMFF where the position
   1884     /// of the content is also encoded.  This function is not compatible with
   1885     /// BMFF hash binding.  If a BMFF data hash or box hash is detected that is
   1886     /// an error.  The DataHash placeholder assertion will be  adjusted to the contain
   1887     /// the correct values.  If the asset_reader value is supplied it will also perform
   1888     /// the hash calculations, otherwise the function uses the caller supplied values.
   1889     /// It is an error if `get_data_hashed_manifest_placeholder` was not called first
   1890     /// as this call inserts the DataHash placeholder assertion to reserve space for the
   1891     /// actual hash values not required when using BoxHashes.
   1892     pub async fn get_data_hashed_embeddable_manifest_async(
   1893         &mut self,
   1894         dh: &DataHash,
   1895         signer: &dyn AsyncSigner,
   1896         format: &str,
   1897         asset_reader: Option<&mut dyn CAIRead>,
   1898     ) -> Result<Vec<u8>> {
   1899         let mut jumbf_bytes =
   1900             self.prep_embeddable_store(signer.reserve_size(), dh, asset_reader)?;
   1901 
   1902         // sign contents
   1903         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   1904         let sig = self
   1905             .sign_claim_async(pc, signer, signer.reserve_size())
   1906             .await?;
   1907 
   1908         let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
   1909 
   1910         self.finish_embeddable_store(&sig, &sig_placeholder, &mut jumbf_bytes, format)
   1911     }
   1912 
   1913     /// Returns a finalized, signed manifest.  The manifest are only supported
   1914     /// for cases when the client has provided a data hash content hash binding.  Note,
   1915     /// this function will not work for cases like BMFF where the position
   1916     /// of the content is also encoded.  This function is not compatible with
   1917     /// BMFF hash binding.  If a BMFF data hash or box hash is detected that is
   1918     /// an error.  The DataHash placeholder assertion will be  adjusted to the contain
   1919     /// the correct values.  If the asset_reader value is supplied it will also perform
   1920     /// the hash calculations, otherwise the function uses the caller supplied values.
   1921     /// It is an error if `get_data_hashed_manifest_placeholder` was not called first
   1922     /// as this call inserts the DataHash placeholder assertion to reserve space for the
   1923     /// actual hash values not required when using BoxHashes.
   1924     pub async fn get_data_hashed_embeddable_manifest_remote(
   1925         &mut self,
   1926         dh: &DataHash,
   1927         signer: &dyn RemoteSigner,
   1928         format: &str,
   1929         asset_reader: Option<&mut dyn CAIRead>,
   1930     ) -> Result<Vec<u8>> {
   1931         let mut jumbf_bytes =
   1932             self.prep_embeddable_store(signer.reserve_size(), dh, asset_reader)?;
   1933 
   1934         // sign contents
   1935         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   1936         let claim_bytes = pc.data()?;
   1937         let sig = signer.sign_remote(&claim_bytes).await?;
   1938 
   1939         let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
   1940 
   1941         self.finish_embeddable_store(&sig, &sig_placeholder, &mut jumbf_bytes, format)
   1942     }
   1943 
   1944     /// Returns a finalized, signed manifest.  The client is required to have
   1945     /// included the necessary box hash assertion with the pregenerated hashes.
   1946     pub fn get_box_hashed_embeddable_manifest(&mut self, signer: &dyn Signer) -> Result<Vec<u8>> {
   1947         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   1948 
   1949         // make sure there is only one
   1950         if pc.hash_assertions().len() != 1 {
   1951             return Err(Error::BadParam(
   1952                 "Claim must have exactly one hash binding assertion".to_string(),
   1953             ));
   1954         }
   1955 
   1956         // only allow box hash assertions to be present
   1957         if pc.box_hash_assertions().is_empty() {
   1958             return Err(Error::BadParam("Missing box hash assertion".to_string()));
   1959         }
   1960 
   1961         let mut jumbf_bytes = self.to_jumbf_internal(signer.reserve_size())?;
   1962 
   1963         // sign contents
   1964         let sig = self.sign_claim(pc, signer, signer.reserve_size())?;
   1965         let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
   1966 
   1967         if sig_placeholder.len() != sig.len() {
   1968             return Err(Error::CoseSigboxTooSmall);
   1969         }
   1970 
   1971         patch_bytes(&mut jumbf_bytes, &sig_placeholder, &sig)
   1972             .map_err(|_| Error::JumbfCreationError)?;
   1973 
   1974         Ok(jumbf_bytes)
   1975     }
   1976 
   1977     /// Returns a finalized, signed manifest.  The client is required to have
   1978     /// included the necessary box hash assertion with the pregenerated hashes.
   1979     pub async fn get_box_hashed_embeddable_manifest_async(
   1980         &mut self,
   1981         signer: &dyn AsyncSigner,
   1982     ) -> Result<Vec<u8>> {
   1983         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   1984 
   1985         // make sure there is only one
   1986         if pc.hash_assertions().len() != 1 {
   1987             return Err(Error::BadParam(
   1988                 "Claim must have exactly one hash binding assertion".to_string(),
   1989             ));
   1990         }
   1991 
   1992         // only allow box hash assertions to be present
   1993         if pc.box_hash_assertions().is_empty() {
   1994             return Err(Error::BadParam("Missing box hash assertion".to_string()));
   1995         }
   1996 
   1997         let mut jumbf_bytes = self.to_jumbf_internal(signer.reserve_size())?;
   1998 
   1999         // sign contents
   2000         let sig = self
   2001             .sign_claim_async(pc, signer, signer.reserve_size())
   2002             .await?;
   2003         let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
   2004 
   2005         if sig_placeholder.len() != sig.len() {
   2006             return Err(Error::CoseSigboxTooSmall);
   2007         }
   2008 
   2009         patch_bytes(&mut jumbf_bytes, &sig_placeholder, &sig)
   2010             .map_err(|_| Error::JumbfCreationError)?;
   2011 
   2012         Ok(jumbf_bytes)
   2013     }
   2014 
   2015     /// Returns the supplied manifest composed to be directly compatible with the desired format.
   2016     /// For example, if format is JPEG function will return the set of APP11 segments that contains
   2017     /// the manifest.  Similarly for PNG it would be the PNG chunk complete with header and  CRC.
   2018     pub fn get_composed_manifest(manifest_bytes: &[u8], format: &str) -> Result<Vec<u8>> {
   2019         if let Some(h) = get_assetio_handler(format) {
   2020             if let Some(composed_data_handler) = h.composed_data_ref() {
   2021                 return composed_data_handler.compose_manifest(manifest_bytes, format);
   2022             }
   2023         }
   2024         Err(Error::UnsupportedType)
   2025     }
   2026 
   2027     /// Embed the claims store as jumbf into a stream. Updates XMP with provenance record.
   2028     /// When called, the stream should contain an asset matching format.
   2029     /// on return, the stream will contain the new manifest signed with signer
   2030     /// This directly modifies the asset in stream, backup stream first if you need to preserve it.
   2031     /// This can also handle remote signing if direct_cose_handling() is true.
   2032     #[async_generic(async_signature(
   2033         &mut self,
   2034         format: &str,
   2035         input_stream: &mut dyn CAIRead,
   2036         output_stream: &mut dyn CAIReadWrite,
   2037         signer: &dyn AsyncSigner,
   2038     ))]
   2039     pub fn save_to_stream(
   2040         &mut self,
   2041         format: &str,
   2042         input_stream: &mut dyn CAIRead,
   2043         output_stream: &mut dyn CAIReadWrite,
   2044         signer: &dyn Signer,
   2045     ) -> Result<Vec<u8>> {
   2046         let intermediate_output: Vec<u8> = Vec::new();
   2047         let mut intermediate_stream = Cursor::new(intermediate_output);
   2048 
   2049         let jumbf_bytes = self.start_save_stream(
   2050             format,
   2051             input_stream,
   2052             &mut intermediate_stream,
   2053             signer.reserve_size(),
   2054         )?;
   2055 
   2056         intermediate_stream.set_position(0);
   2057         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   2058         let sig = if _sync {
   2059             self.sign_claim(pc, signer, signer.reserve_size())
   2060         } else {
   2061             self.sign_claim_async(pc, signer, signer.reserve_size())
   2062                 .await
   2063         }?;
   2064         let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
   2065 
   2066         intermediate_stream.rewind()?;
   2067         match self.finish_save_stream(
   2068             jumbf_bytes,
   2069             format,
   2070             &mut intermediate_stream,
   2071             output_stream,
   2072             sig,
   2073             &sig_placeholder,
   2074         ) {
   2075             Ok((s, m)) => {
   2076                 // save sig so store is up to date
   2077                 let pc_mut = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   2078                 pc_mut.set_signature_val(s);
   2079 
   2080                 Ok(m)
   2081             }
   2082             Err(e) => Err(e),
   2083         }
   2084     }
   2085 
   2086     /// Async RemoteSigner used to embed the claims store and  returns memory representation of the
   2087     /// asset and manifest. Updates XMP with provenance record.
   2088     /// When called, the stream should contain an asset matching format.
   2089     /// Returns a tuple (output asset, manifest store) with a `Vec<u8>` containing the output asset and a `Vec<u8>` containing the insert manifest store.  (output asset, )
   2090     pub(crate) async fn save_to_memory_remote_signed(
   2091         &mut self,
   2092         format: &str,
   2093         asset: &[u8],
   2094         remote_signer: &dyn crate::signer::RemoteSigner,
   2095     ) -> Result<(Vec<u8>, Vec<u8>)> {
   2096         let mut input_stream = Cursor::new(asset);
   2097         let output_vec: Vec<u8> = Vec::new();
   2098         let mut output_stream = Cursor::new(output_vec);
   2099 
   2100         let jumbf_bytes = self.start_save_stream(
   2101             format,
   2102             &mut input_stream,
   2103             &mut output_stream,
   2104             remote_signer.reserve_size(),
   2105         )?;
   2106 
   2107         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   2108         let sig = remote_signer.sign_remote(&pc.data()?).await?;
   2109         let sig_placeholder = Store::sign_claim_placeholder(pc, remote_signer.reserve_size());
   2110 
   2111         match self.finish_save_to_memory(
   2112             jumbf_bytes,
   2113             format,
   2114             &output_stream.into_inner(),
   2115             sig,
   2116             &sig_placeholder,
   2117         ) {
   2118             Ok((s, output_asset, output_jumbf)) => {
   2119                 // save sig so store is up to date
   2120                 let pc_mut = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   2121                 pc_mut.set_signature_val(s);
   2122 
   2123                 Ok((output_asset, output_jumbf))
   2124             }
   2125             Err(e) => Err(e),
   2126         }
   2127     }
   2128 
   2129     /// Embed the claims store as jumbf into an asset. Updates XMP with provenance record.
   2130     #[cfg(feature = "file_io")]
   2131     pub fn save_to_asset(
   2132         &mut self,
   2133         asset_path: &Path,
   2134         signer: &dyn Signer,
   2135         dest_path: &Path,
   2136     ) -> Result<()> {
   2137         // set up temp dir, contents auto deleted
   2138         let td = tempfile::TempDir::new()?;
   2139         let temp_path = td.path();
   2140         let temp_file = temp_path.join(
   2141             dest_path
   2142                 .file_name()
   2143                 .ok_or_else(|| Error::BadParam("invalid destination path".to_string()))?,
   2144         );
   2145 
   2146         let jumbf_bytes = self.start_save(asset_path, &temp_file, signer.reserve_size())?;
   2147 
   2148         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   2149         let sig = self.sign_claim(pc, signer, signer.reserve_size())?;
   2150         let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
   2151 
   2152         // get correct output path for remote manifest
   2153         let output_path = match pc.remote_manifest() {
   2154             RemoteManifest::NoRemote | RemoteManifest::EmbedWithRemote(_) => {
   2155                 temp_file.to_path_buf()
   2156             }
   2157             RemoteManifest::SideCar | RemoteManifest::Remote(_) => {
   2158                 temp_file.with_extension(MANIFEST_STORE_EXT)
   2159             }
   2160         };
   2161 
   2162         match self.finish_save(jumbf_bytes, &output_path, sig, &sig_placeholder) {
   2163             Ok((s, m)) => {
   2164                 // save sig so store is up to date
   2165                 let pc_mut = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   2166                 pc_mut.set_signature_val(s);
   2167 
   2168                 // do we need to make a C2PA file in addition to standard embedded output
   2169                 if let RemoteManifest::EmbedWithRemote(_url) = pc_mut.remote_manifest() {
   2170                     let c2pa = output_path.with_extension(MANIFEST_STORE_EXT);
   2171                     std::fs::write(c2pa, &m)?;
   2172                 }
   2173 
   2174                 // copy the correct files upon completion
   2175                 Store::copy_c2pa_to_output(&temp_file, dest_path, pc_mut.remote_manifest())?;
   2176 
   2177                 Ok(())
   2178             }
   2179             Err(e) => Err(e),
   2180         }
   2181     }
   2182 
   2183     /// Embed the claims store as jumbf into an asset using an async signer. Updates XMP with provenance record.
   2184     #[cfg(feature = "file_io")]
   2185     pub async fn save_to_asset_async(
   2186         &mut self,
   2187         asset_path: &Path,
   2188         signer: &dyn AsyncSigner,
   2189         dest_path: &Path,
   2190     ) -> Result<Vec<u8>> {
   2191         // set up temp dir, contents auto deleted
   2192         let td = tempfile::TempDir::new()?;
   2193         let temp_path = td.path();
   2194         let temp_file = temp_path.join(
   2195             dest_path
   2196                 .file_name()
   2197                 .ok_or_else(|| Error::BadParam("invalid destination path".to_string()))?,
   2198         );
   2199 
   2200         let jumbf_bytes = self.start_save(asset_path, &temp_file, signer.reserve_size())?;
   2201 
   2202         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   2203         let sig = self
   2204             .sign_claim_async(pc, signer, signer.reserve_size())
   2205             .await?;
   2206         let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
   2207 
   2208         // get correct output path for remote manifest
   2209         let output_path = match pc.remote_manifest() {
   2210             RemoteManifest::NoRemote | RemoteManifest::EmbedWithRemote(_) => {
   2211                 temp_file.to_path_buf()
   2212             }
   2213             RemoteManifest::SideCar | RemoteManifest::Remote(_) => {
   2214                 temp_file.with_extension(MANIFEST_STORE_EXT)
   2215             }
   2216         };
   2217 
   2218         match self.finish_save(jumbf_bytes, &output_path, sig, &sig_placeholder) {
   2219             Ok((s, m)) => {
   2220                 // save sig so store is up to date
   2221                 let pc_mut = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   2222                 pc_mut.set_signature_val(s);
   2223 
   2224                 // do we need to make a C2PA file in addition to standard embedded output
   2225                 if let RemoteManifest::EmbedWithRemote(_url) = pc_mut.remote_manifest() {
   2226                     let c2pa = output_path.with_extension(MANIFEST_STORE_EXT);
   2227                     std::fs::write(c2pa, &m)?;
   2228                 }
   2229 
   2230                 // copy the correct files upon completion
   2231                 Store::copy_c2pa_to_output(&temp_file, dest_path, pc_mut.remote_manifest())?;
   2232 
   2233                 Ok(m)
   2234             }
   2235             Err(e) => Err(e),
   2236         }
   2237     }
   2238 
   2239     /// Embed the claims store as jumbf into an asset using an CoseSign box generated remotely. Updates XMP with provenance record.
   2240     #[cfg(feature = "file_io")]
   2241     pub async fn save_to_asset_remote_signed(
   2242         &mut self,
   2243         asset_path: &Path,
   2244         remote_signer: &dyn crate::signer::RemoteSigner,
   2245         dest_path: &Path,
   2246     ) -> Result<Vec<u8>> {
   2247         // set up temp dir, contents auto deleted
   2248         let td = tempfile::TempDir::new()?;
   2249         let temp_path = td.path();
   2250         let temp_file = temp_path.join(
   2251             dest_path
   2252                 .file_name()
   2253                 .ok_or_else(|| Error::BadParam("invalid destination path".to_string()))?,
   2254         );
   2255 
   2256         let jumbf_bytes = self.start_save(asset_path, &temp_file, remote_signer.reserve_size())?;
   2257 
   2258         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   2259         let sig = remote_signer.sign_remote(&pc.data()?).await?;
   2260 
   2261         let sig_placeholder = Store::sign_claim_placeholder(pc, remote_signer.reserve_size());
   2262 
   2263         // get correct output path for remote manifest
   2264         let output_path = match pc.remote_manifest() {
   2265             RemoteManifest::NoRemote | RemoteManifest::EmbedWithRemote(_) => {
   2266                 temp_file.to_path_buf()
   2267             }
   2268             RemoteManifest::SideCar | RemoteManifest::Remote(_) => {
   2269                 temp_file.with_extension(MANIFEST_STORE_EXT)
   2270             }
   2271         };
   2272 
   2273         match self.finish_save(jumbf_bytes, &output_path, sig, &sig_placeholder) {
   2274             Ok((s, m)) => {
   2275                 // save sig so store is up to date
   2276                 let pc_mut = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   2277                 pc_mut.set_signature_val(s);
   2278 
   2279                 // do we need to make a C2PA file in addition to standard embedded output
   2280                 if let RemoteManifest::EmbedWithRemote(_url) = pc_mut.remote_manifest() {
   2281                     let c2pa = output_path.with_extension(MANIFEST_STORE_EXT);
   2282                     std::fs::write(c2pa, &m)?;
   2283                 }
   2284 
   2285                 // copy the correct files upon completion
   2286                 Store::copy_c2pa_to_output(&temp_file, dest_path, pc_mut.remote_manifest())?;
   2287 
   2288                 Ok(m)
   2289             }
   2290             Err(e) => Err(e),
   2291         }
   2292     }
   2293 
   2294     fn start_save_stream(
   2295         &mut self,
   2296         format: &str,
   2297         input_stream: &mut dyn CAIRead,
   2298         output_stream: &mut dyn CAIReadWrite,
   2299         reserve_size: usize,
   2300     ) -> Result<Vec<u8>> {
   2301         let intermediate_output: Vec<u8> = Vec::new();
   2302         let mut intermediate_stream = Cursor::new(intermediate_output);
   2303 
   2304         let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   2305 
   2306         // Add remote reference XMP if needed and strip out existing manifest
   2307         // We don't need to strip manifests if we are replacing an exsiting one
   2308         let (url, remove_manifests) = match pc.remote_manifest() {
   2309             RemoteManifest::NoRemote => (None, false),
   2310             RemoteManifest::SideCar => (None, true),
   2311             RemoteManifest::Remote(url) => (Some(url), true),
   2312             RemoteManifest::EmbedWithRemote(url) => (Some(url), false),
   2313         };
   2314 
   2315         let io_handler = get_assetio_handler(format).ok_or(Error::UnsupportedType)?;
   2316 
   2317         // Do not assume the handler supports XMP or removing manifests unless we need it to
   2318         if let Some(url) = url {
   2319             let external_ref_writer = io_handler
   2320                 .remote_ref_writer_ref()
   2321                 .ok_or(Error::XmpNotSupported)?;
   2322 
   2323             if remove_manifests {
   2324                 let manifest_writer = io_handler
   2325                     .get_writer(format)
   2326                     .ok_or(Error::UnsupportedType)?;
   2327 
   2328                 let tmp_output: Vec<u8> = Vec::new();
   2329                 let mut tmp_stream = Cursor::new(tmp_output);
   2330                 manifest_writer.remove_cai_store_from_stream(input_stream, &mut tmp_stream)?;
   2331 
   2332                 // add external ref if possible
   2333                 tmp_stream.set_position(0);
   2334                 external_ref_writer.embed_reference_to_stream(
   2335                     &mut tmp_stream,
   2336                     &mut intermediate_stream,
   2337                     RemoteRefEmbedType::Xmp(url),
   2338                 )?;
   2339             } else {
   2340                 // add external ref if possible
   2341                 external_ref_writer.embed_reference_to_stream(
   2342                     input_stream,
   2343                     &mut intermediate_stream,
   2344                     RemoteRefEmbedType::Xmp(url),
   2345                 )?;
   2346             }
   2347         } else if remove_manifests {
   2348             let manifest_writer = io_handler
   2349                 .get_writer(format)
   2350                 .ok_or(Error::UnsupportedType)?;
   2351 
   2352             manifest_writer.remove_cai_store_from_stream(input_stream, &mut intermediate_stream)?;
   2353         } else {
   2354             // just clone stream
   2355             input_stream.rewind()?;
   2356             std::io::copy(input_stream, &mut intermediate_stream)?;
   2357         }
   2358 
   2359         let is_bmff = is_bmff_format(format);
   2360 
   2361         let mut data;
   2362         let jumbf_size;
   2363 
   2364         if is_bmff {
   2365             // 2) Get hash ranges if needed, do not generate for update manifests
   2366             if !pc.update_manifest() {
   2367                 intermediate_stream.rewind()?;
   2368                 let bmff_hashes =
   2369                     Store::generate_bmff_data_hashes(&mut intermediate_stream, pc.alg(), false)?;
   2370                 for hash in bmff_hashes {
   2371                     pc.add_assertion(&hash)?;
   2372                 }
   2373             }
   2374 
   2375             // 3) Generate in memory CAI jumbf block
   2376             // and write preliminary jumbf store to file
   2377             // source and dest the same so save_jumbf_to_file will use the same file since we have already cloned
   2378             data = self.to_jumbf_internal(reserve_size)?;
   2379             jumbf_size = data.len();
   2380             // write the jumbf to the output stream if we are embedding the manifest
   2381             if !remove_manifests {
   2382                 intermediate_stream.rewind()?;
   2383                 save_jumbf_to_stream(format, &mut intermediate_stream, output_stream, &data)?;
   2384             } else {
   2385                 // just copy the asset to the output stream without an embedded manifest (may be stripping one out here)
   2386                 intermediate_stream.rewind()?;
   2387                 std::io::copy(&mut intermediate_stream, output_stream)?;
   2388             }
   2389 
   2390             // generate actual hash values
   2391             let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?; // reborrow to change mutability
   2392 
   2393             if !pc.update_manifest() {
   2394                 let bmff_hashes = pc.bmff_hash_assertions();
   2395 
   2396                 if !bmff_hashes.is_empty() {
   2397                     let mut bmff_hash = BmffHash::from_assertion(bmff_hashes[0])?;
   2398                     intermediate_stream.rewind()?;
   2399                     output_stream.rewind()?;
   2400                     std::io::copy(output_stream, &mut intermediate_stream)?; // remove this once we can get a CAIReader from CAIReadWrite safely
   2401                     bmff_hash.gen_hash_from_stream(&mut intermediate_stream)?;
   2402                     pc.update_bmff_hash(bmff_hash)?;
   2403                 }
   2404             }
   2405         } else {
   2406             // we will not do automatic hashing if we detect a box hash present
   2407             let mut needs_hashing = false;
   2408             if pc.hash_assertions().is_empty() {
   2409                 // 2) Get hash ranges if needed, do not generate for update manifests
   2410                 let mut hash_ranges =
   2411                     object_locations_from_stream(format, &mut intermediate_stream)?;
   2412                 let hashes: Vec<DataHash> = if pc.update_manifest() {
   2413                     Vec::new()
   2414                 } else {
   2415                     Store::generate_data_hashes_for_stream(
   2416                         &mut intermediate_stream,
   2417                         pc.alg(),
   2418                         &mut hash_ranges,
   2419                         false,
   2420                     )?
   2421                 };
   2422 
   2423                 // add the placeholder data hashes to provenance claim so that the required space is reserved
   2424                 for mut hash in hashes {
   2425                     // add padding to account for possible cbor expansion of final DataHash
   2426                     let padding: Vec<u8> = vec![0x0; 10];
   2427                     hash.add_padding(padding);
   2428 
   2429                     pc.add_assertion(&hash)?;
   2430                 }
   2431                 needs_hashing = true;
   2432             }
   2433 
   2434             // 3) Generate in memory CAI jumbf block
   2435             data = self.to_jumbf_internal(reserve_size)?;
   2436             jumbf_size = data.len();
   2437 
   2438             // write the jumbf to the output stream if we are embedding the manifest
   2439             if !remove_manifests {
   2440                 intermediate_stream.rewind()?;
   2441                 save_jumbf_to_stream(format, &mut intermediate_stream, output_stream, &data)?;
   2442             } else {
   2443                 // just copy the asset to the output stream without an embedded manifest (may be stripping one out here)
   2444                 intermediate_stream.rewind()?;
   2445                 std::io::copy(&mut intermediate_stream, output_stream)?;
   2446             }
   2447 
   2448             // 4)  determine final object locations and patch the asset hashes with correct offset
   2449             // replace the source with correct asset hashes so that the claim hash will be correct
   2450             if needs_hashing {
   2451                 let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   2452 
   2453                 // get the final hash ranges, but not for update manifests
   2454                 intermediate_stream.rewind()?;
   2455                 output_stream.rewind()?;
   2456                 std::io::copy(output_stream, &mut intermediate_stream)?; // can remove this once we can get a CAIReader from CAIReadWrite safely
   2457                 let mut new_hash_ranges =
   2458                     object_locations_from_stream(format, &mut intermediate_stream)?;
   2459                 let updated_hashes = if pc.update_manifest() {
   2460                     Vec::new()
   2461                 } else {
   2462                     Store::generate_data_hashes_for_stream(
   2463                         &mut intermediate_stream,
   2464                         pc.alg(),
   2465                         &mut new_hash_ranges,
   2466                         true,
   2467                     )?
   2468                 };
   2469 
   2470                 // patch existing claim hash with updated data
   2471                 for hash in updated_hashes {
   2472                     pc.update_data_hash(hash)?;
   2473                 }
   2474             }
   2475         }
   2476 
   2477         // regenerate the jumbf because the cbor changed
   2478         data = self.to_jumbf_internal(reserve_size)?;
   2479         if jumbf_size != data.len() {
   2480             return Err(Error::JumbfCreationError);
   2481         }
   2482 
   2483         Ok(data) // return JUMBF data
   2484     }
   2485 
   2486     fn finish_save_stream(
   2487         &self,
   2488         mut jumbf_bytes: Vec<u8>,
   2489         format: &str,
   2490         input_stream: &mut dyn CAIRead,
   2491         output_stream: &mut dyn CAIReadWrite,
   2492         sig: Vec<u8>,
   2493         sig_placeholder: &[u8],
   2494     ) -> Result<(Vec<u8>, Vec<u8>)> {
   2495         if sig_placeholder.len() != sig.len() {
   2496             return Err(Error::CoseSigboxTooSmall);
   2497         }
   2498 
   2499         patch_bytes(&mut jumbf_bytes, sig_placeholder, &sig)
   2500             .map_err(|_| Error::JumbfCreationError)?;
   2501 
   2502         // re-save to file
   2503         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   2504         match pc.remote_manifest() {
   2505             RemoteManifest::NoRemote | RemoteManifest::EmbedWithRemote(_) => {
   2506                 save_jumbf_to_stream(format, input_stream, output_stream, &jumbf_bytes)?;
   2507             }
   2508             RemoteManifest::SideCar | RemoteManifest::Remote(_) => {
   2509                 // just copy the asset to the output stream without an embedded manifest (may be stripping one out here)
   2510                 std::io::copy(input_stream, output_stream)?;
   2511             }
   2512         }
   2513 
   2514         Ok((sig, jumbf_bytes))
   2515     }
   2516 
   2517     fn finish_save_to_memory(
   2518         &self,
   2519         mut jumbf_bytes: Vec<u8>,
   2520         format: &str,
   2521         source_asset: &[u8],
   2522         sig: Vec<u8>,
   2523         sig_placeholder: &[u8],
   2524     ) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
   2525         if sig_placeholder.len() != sig.len() {
   2526             return Err(Error::CoseSigboxTooSmall);
   2527         }
   2528 
   2529         patch_bytes(&mut jumbf_bytes, sig_placeholder, &sig)
   2530             .map_err(|_| Error::JumbfCreationError)?;
   2531 
   2532         // return sig and output
   2533         Ok((
   2534             sig,
   2535             save_jumbf_to_memory(format, source_asset, &jumbf_bytes)?,
   2536             jumbf_bytes,
   2537         ))
   2538     }
   2539 
   2540     #[cfg(feature = "file_io")]
   2541     fn start_save(
   2542         &mut self,
   2543         asset_path: &Path,
   2544         dest_path: &Path,
   2545         reserve_size: usize,
   2546     ) -> Result<Vec<u8>> {
   2547         // force generate external manifests for unknown types
   2548 
   2549         let ext = match get_supported_file_extension(dest_path) {
   2550             Some(ext) => ext,
   2551             None => {
   2552                 let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   2553                 pc.set_external_manifest(); // generate external manifests for unknown types
   2554                 MANIFEST_STORE_EXT.to_owned()
   2555             }
   2556         };
   2557 
   2558         // clone the source to working copy if requested
   2559         if asset_path != dest_path {
   2560             fs::copy(asset_path, dest_path).map_err(Error::IoError)?;
   2561         }
   2562 
   2563         //  update file following the steps outlined in CAI spec
   2564 
   2565         // 1) Add DC provenance XMP
   2566         let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
   2567         let output_path = match pc.remote_manifest() {
   2568             crate::claim::RemoteManifest::NoRemote => dest_path.to_path_buf(),
   2569             crate::claim::RemoteManifest::SideCar => {
   2570                 // remove any previous c2pa manifest from the asset
   2571                 match remove_jumbf_from_file(dest_path) {
   2572                     Ok(_) | Err(Error::UnsupportedType) => {
   2573                         dest_path.with_extension(MANIFEST_STORE_EXT)
   2574                     }
   2575                     Err(e) => return Err(e),
   2576                 }
   2577             }
   2578             crate::claim::RemoteManifest::Remote(url) => {
   2579                 let d = dest_path.with_extension(MANIFEST_STORE_EXT);
   2580                 // remove any previous c2pa manifest from the asset
   2581                 remove_jumbf_from_file(dest_path)?;
   2582 
   2583                 if let Some(h) = get_assetio_handler(&ext) {
   2584                     if let Some(external_ref_writer) = h.remote_ref_writer_ref() {
   2585                         external_ref_writer
   2586                             .embed_reference(dest_path, RemoteRefEmbedType::Xmp(url))?;
   2587                     } else {
   2588                         return Err(Error::XmpNotSupported);
   2589                     }
   2590                 } else {
   2591                     return Err(Error::UnsupportedType);
   2592                 }
   2593 
   2594                 d
   2595             }
   2596             crate::claim::RemoteManifest::EmbedWithRemote(url) => {
   2597                 if let Some(h) = get_assetio_handler(&ext) {
   2598                     if let Some(external_ref_writer) = h.remote_ref_writer_ref() {
   2599                         external_ref_writer
   2600                             .embed_reference(dest_path, RemoteRefEmbedType::Xmp(url))?;
   2601                     } else {
   2602                         return Err(Error::XmpNotSupported);
   2603                     }
   2604                 } else {
   2605                     return Err(Error::UnsupportedType);
   2606                 }
   2607                 dest_path.to_path_buf()
   2608             }
   2609         };
   2610 
   2611         // get the provenance claim changing mutability
   2612         let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   2613 
   2614         let is_bmff = is_bmff_format(&ext);
   2615 
   2616         let mut data;
   2617         let jumbf_size;
   2618 
   2619         if is_bmff {
   2620             // 2) Get hash ranges if needed, do not generate for update manifests
   2621             if !pc.update_manifest() {
   2622                 let mut file = std::fs::File::open(asset_path)?;
   2623                 let bmff_hashes = Store::generate_bmff_data_hashes(&mut file, pc.alg(), false)?;
   2624                 for hash in bmff_hashes {
   2625                     pc.add_assertion(&hash)?;
   2626                 }
   2627             }
   2628 
   2629             // 3) Generate in memory CAI jumbf block
   2630             // and write preliminary jumbf store to file
   2631             // source and dest the same so save_jumbf_to_file will use the same file since we have already cloned
   2632             data = self.to_jumbf_internal(reserve_size)?;
   2633             jumbf_size = data.len();
   2634             save_jumbf_to_file(&data, &output_path, Some(&output_path))?;
   2635 
   2636             // generate actual hash values
   2637             let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?; // reborrow to change mutability
   2638 
   2639             if !pc.update_manifest() {
   2640                 let bmff_hashes = pc.bmff_hash_assertions();
   2641 
   2642                 if !bmff_hashes.is_empty() {
   2643                     let mut bmff_hash = BmffHash::from_assertion(bmff_hashes[0])?;
   2644                     bmff_hash.gen_hash(dest_path)?;
   2645                     pc.update_bmff_hash(bmff_hash)?;
   2646                 }
   2647             }
   2648         } else {
   2649             // we will not do automatic hashing if we detect a box hash present
   2650             let mut needs_hashing = false;
   2651             if pc.box_hash_assertions().is_empty() {
   2652                 // 2) Get hash ranges if needed, do not generate for update manifests
   2653                 let mut hash_ranges = object_locations(&output_path)?;
   2654                 let hashes: Vec<DataHash> = if pc.update_manifest() {
   2655                     Vec::new()
   2656                 } else {
   2657                     Store::generate_data_hashes(dest_path, pc.alg(), &mut hash_ranges, false)?
   2658                 };
   2659 
   2660                 // add the placeholder data hashes to provenance claim so that the required space is reserved
   2661                 for mut hash in hashes {
   2662                     // add padding to account for possible cbor expansion of final DataHash
   2663                     let padding: Vec<u8> = vec![0x0; 10];
   2664                     hash.add_padding(padding);
   2665 
   2666                     pc.add_assertion(&hash)?;
   2667                 }
   2668                 needs_hashing = true;
   2669             }
   2670 
   2671             // 3) Generate in memory CAI jumbf block
   2672             // and write preliminary jumbf store to file
   2673             // source and dest the same so save_jumbf_to_file will use the same file since we have already cloned
   2674             data = self.to_jumbf_internal(reserve_size)?;
   2675             jumbf_size = data.len();
   2676             save_jumbf_to_file(&data, &output_path, Some(&output_path))?;
   2677 
   2678             // 4)  determine final object locations and patch the asset hashes with correct offset
   2679             // replace the source with correct asset hashes so that the claim hash will be correct
   2680             // If box hash is present we don't do any other
   2681             if needs_hashing {
   2682                 let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
   2683 
   2684                 // get the final hash ranges, but not for update manifests
   2685                 let mut new_hash_ranges = object_locations(&output_path)?;
   2686                 let updated_hashes = if pc.update_manifest() {
   2687                     Vec::new()
   2688                 } else {
   2689                     Store::generate_data_hashes(dest_path, pc.alg(), &mut new_hash_ranges, true)?
   2690                 };
   2691 
   2692                 // patch existing claim hash with updated data
   2693                 for hash in updated_hashes {
   2694                     pc.update_data_hash(hash)?;
   2695                 }
   2696             }
   2697         }
   2698 
   2699         // regenerate the jumbf because the cbor changed
   2700         data = self.to_jumbf_internal(reserve_size)?;
   2701         if jumbf_size != data.len() {
   2702             return Err(Error::JumbfCreationError);
   2703         }
   2704 
   2705         Ok(data) // return JUMBF data
   2706     }
   2707 
   2708     #[cfg(feature = "file_io")]
   2709     fn finish_save(
   2710         &self,
   2711         mut jumbf_bytes: Vec<u8>,
   2712         output_path: &Path,
   2713         sig: Vec<u8>,
   2714         sig_placeholder: &[u8],
   2715     ) -> Result<(Vec<u8>, Vec<u8>)> {
   2716         if sig_placeholder.len() != sig.len() {
   2717             return Err(Error::CoseSigboxTooSmall);
   2718         }
   2719 
   2720         patch_bytes(&mut jumbf_bytes, sig_placeholder, &sig)
   2721             .map_err(|_| Error::JumbfCreationError)?;
   2722 
   2723         // re-save to file
   2724         save_jumbf_to_file(&jumbf_bytes, output_path, Some(output_path))?;
   2725 
   2726         Ok((sig, jumbf_bytes))
   2727     }
   2728 
   2729     /// Verify Store from an existing asset
   2730     /// asset_path: path to input asset
   2731     /// validation_log: If present all found errors are logged and returned, otherwise first error causes exit and is returned
   2732     #[cfg(feature = "file_io")]
   2733     pub fn verify_from_path(
   2734         &mut self,
   2735         asset_path: &'_ Path,
   2736         validation_log: &mut impl StatusTracker,
   2737     ) -> Result<()> {
   2738         Store::verify_store(self, &mut ClaimAssetData::Path(asset_path), validation_log)
   2739     }
   2740 
   2741     // verify from a buffer without file i/o
   2742     pub fn verify_from_buffer(
   2743         &mut self,
   2744         buf: &[u8],
   2745         asset_type: &str,
   2746         validation_log: &mut impl StatusTracker,
   2747     ) -> Result<()> {
   2748         Store::verify_store(
   2749             self,
   2750             &mut ClaimAssetData::Bytes(buf, asset_type),
   2751             validation_log,
   2752         )
   2753     }
   2754 
   2755     // verify from a buffer without file i/o
   2756     pub fn verify_from_stream(
   2757         &mut self,
   2758         reader: &mut dyn CAIRead,
   2759         asset_type: &str,
   2760         validation_log: &mut impl StatusTracker,
   2761     ) -> Result<()> {
   2762         Store::verify_store(
   2763             self,
   2764             &mut ClaimAssetData::Stream(reader, asset_type),
   2765             validation_log,
   2766         )
   2767     }
   2768 
   2769     // fetch remote manifest if possible
   2770     #[cfg(feature = "fetch_remote_manifests")]
   2771     fn fetch_remote_manifest(url: &str) -> Result<Vec<u8>> {
   2772         use conv::ValueFrom;
   2773         use ureq::Error as uError;
   2774 
   2775         //const MANIFEST_CONTENT_TYPE: &str = "application/x-c2pa-manifest-store"; // todo verify once these are served
   2776         const DEFAULT_MANIFEST_RESPONSE_SIZE: usize = 10 * 1024 * 1024; // 10 MB
   2777 
   2778         match ureq::get(url).call() {
   2779             Ok(response) => {
   2780                 if response.status() == 200 {
   2781                     let len = response
   2782                         .header("Content-Length")
   2783                         .and_then(|s| s.parse::<usize>().ok())
   2784                         .unwrap_or(DEFAULT_MANIFEST_RESPONSE_SIZE); // todo figure out good max to accept
   2785 
   2786                     let mut response_bytes: Vec<u8> = Vec::with_capacity(len);
   2787 
   2788                     let len64 = u64::value_from(len)
   2789                         .map_err(|_err| Error::BadParam("value out of range".to_string()))?;
   2790 
   2791                     response
   2792                         .into_reader()
   2793                         .take(len64)
   2794                         .read_to_end(&mut response_bytes)
   2795                         .map_err(|_err| {
   2796                             Error::RemoteManifestFetch("error reading content stream".to_string())
   2797                         })?;
   2798 
   2799                     Ok(response_bytes)
   2800                 } else {
   2801                     Err(Error::RemoteManifestFetch(format!(
   2802                         "fetch failed: code: {}, status: {}",
   2803                         response.status(),
   2804                         response.status_text()
   2805                     )))
   2806                 }
   2807             }
   2808             Err(uError::Status(code, resp)) => Err(Error::RemoteManifestFetch(format!(
   2809                 "code: {}, response: {}",
   2810                 code,
   2811                 resp.status_text()
   2812             ))),
   2813             Err(uError::Transport(_)) => Err(Error::RemoteManifestFetch(url.to_string())),
   2814         }
   2815     }
   2816 
   2817     /// Handles remote manifests when file_io/fetch_remote_manifests feature is enabled
   2818     fn handle_remote_manifest(ext_ref: &str) -> Result<Vec<u8>> {
   2819         // verify provenance path is remote url
   2820         if Store::is_valid_remote_url(ext_ref) {
   2821             #[cfg(feature = "fetch_remote_manifests")]
   2822             {
   2823                 Store::fetch_remote_manifest(ext_ref)
   2824             }
   2825             #[cfg(not(feature = "fetch_remote_manifests"))]
   2826             Err(Error::RemoteManifestUrl(ext_ref.to_owned()))
   2827         } else {
   2828             Err(Error::JumbfNotFound)
   2829         }
   2830     }
   2831 
   2832     /// Return Store from in memory asset
   2833     fn load_cai_from_memory(
   2834         asset_type: &str,
   2835         data: &[u8],
   2836         validation_log: &mut impl StatusTracker,
   2837     ) -> Result<Store> {
   2838         let mut input_stream = Cursor::new(data);
   2839         Store::load_jumbf_from_stream(asset_type, &mut input_stream)
   2840             .map(|manifest_bytes| Store::from_jumbf(&manifest_bytes, validation_log))?
   2841     }
   2842 
   2843     /// load jumbf given a stream
   2844     ///
   2845     /// This handles, embedded and remote manifests
   2846     ///
   2847     /// asset_type -  mime type of the stream
   2848     /// stream - a readable stream of an asset
   2849     pub fn load_jumbf_from_stream(asset_type: &str, stream: &mut dyn CAIRead) -> Result<Vec<u8>> {
   2850         match load_jumbf_from_stream(asset_type, stream) {
   2851             Ok(manifest_bytes) => Ok(manifest_bytes),
   2852             Err(Error::JumbfNotFound) => {
   2853                 stream.rewind()?;
   2854                 if let Some(ext_ref) =
   2855                     crate::utils::xmp_inmemory_utils::XmpInfo::from_source(stream, asset_type)
   2856                         .provenance
   2857                 {
   2858                     Store::handle_remote_manifest(&ext_ref)
   2859                 } else {
   2860                     Err(Error::JumbfNotFound)
   2861                 }
   2862             }
   2863             Err(e) => Err(e),
   2864         }
   2865     }
   2866 
   2867     /// load jumbf given a file path
   2868     ///
   2869     /// This handles, embedded, sidecar and remote manifests
   2870     ///
   2871     /// in_path -  path to source file
   2872     /// validation_log - optional vec to contain addition info about the asset
   2873     #[cfg(feature = "file_io")]
   2874     pub fn load_jumbf_from_path(in_path: &Path) -> Result<Vec<u8>> {
   2875         let external_manifest = in_path.with_extension(MANIFEST_STORE_EXT);
   2876         let external_exists = external_manifest.exists();
   2877 
   2878         match load_jumbf_from_file(in_path) {
   2879             Ok(manifest_bytes) => Ok(manifest_bytes),
   2880             Err(Error::UnsupportedType) => {
   2881                 if external_exists {
   2882                     std::fs::read(external_manifest).map_err(Error::IoError)
   2883                 } else {
   2884                     Err(Error::UnsupportedType)
   2885                 }
   2886             }
   2887             Err(Error::JumbfNotFound) => {
   2888                 if external_exists {
   2889                     std::fs::read(external_manifest).map_err(Error::IoError)
   2890                 } else {
   2891                     // check for remote manifest
   2892                     let mut asset_reader = std::fs::File::open(in_path)?;
   2893                     let ext = get_file_extension(in_path).ok_or(Error::UnsupportedType)?;
   2894                     if let Some(ext_ref) = crate::utils::xmp_inmemory_utils::XmpInfo::from_source(
   2895                         &mut asset_reader,
   2896                         &ext,
   2897                     )
   2898                     .provenance
   2899                     {
   2900                         Store::handle_remote_manifest(&ext_ref)
   2901                     } else {
   2902                         Err(Error::JumbfNotFound)
   2903                     }
   2904                 }
   2905             }
   2906             Err(e) => Err(e),
   2907         }
   2908     }
   2909 
   2910     /// load a CAI store from  a file
   2911     ///
   2912     /// in_path -  path to source file
   2913     /// validation_log - optional vec to contain addition info about the asset
   2914     #[cfg(feature = "file_io")]
   2915     fn load_cai_from_file(
   2916         in_path: &Path,
   2917         validation_log: &mut impl StatusTracker,
   2918     ) -> Result<Store> {
   2919         match Self::load_jumbf_from_path(in_path) {
   2920             Ok(manifest_bytes) => {
   2921                 // load and validate with CAI toolkit
   2922                 Store::from_jumbf(&manifest_bytes, validation_log)
   2923             }
   2924             Err(e) => Err(e),
   2925         }
   2926     }
   2927 
   2928     /// Load Store from claims in an existing asset
   2929     /// asset_path: path to input asset
   2930     /// verify: determines whether to verify the contents of the provenance claim.  Must be set true to use validation_log
   2931     /// validation_log: If present all found errors are logged and returned, otherwise first error causes exit and is returned
   2932     #[cfg(feature = "file_io")]
   2933     pub fn load_from_asset(
   2934         asset_path: &Path,
   2935         verify: bool,
   2936         validation_log: &mut impl StatusTracker,
   2937     ) -> Result<Store> {
   2938         // load jumbf if available
   2939         Self::load_cai_from_file(asset_path, validation_log)
   2940             .and_then(|mut store| {
   2941                 // verify the store
   2942                 if verify {
   2943                     store.verify_from_path(asset_path, validation_log)?;
   2944                 }
   2945 
   2946                 Ok(store)
   2947             })
   2948             .map_err(|e| {
   2949                 validation_log.log_silent(
   2950                     log_item!("asset", "error loading file", "load_from_asset").set_error(&e),
   2951                 );
   2952                 e
   2953             })
   2954     }
   2955 
   2956     pub fn get_store_from_memory(
   2957         asset_type: &str,
   2958         data: &[u8],
   2959         validation_log: &mut impl StatusTracker,
   2960     ) -> Result<Store> {
   2961         // load jumbf if available
   2962         Self::load_cai_from_memory(asset_type, data, validation_log).map_err(|e| {
   2963             validation_log.log_silent(
   2964                 log_item!("asset", "error loading asset", "get_store_from_memory").set_error(&e),
   2965             );
   2966             e
   2967         })
   2968     }
   2969 
   2970     /// Returns embedded remote manifest URL if available
   2971     /// asset_type: extensions or mime type of the data
   2972     /// data: byte array containing the asset
   2973     pub fn get_remote_manifest_url(asset_type: &str, data: &[u8]) -> Option<String> {
   2974         let mut buf_reader = Cursor::new(data);
   2975 
   2976         if let Some(ext_ref) =
   2977             crate::utils::xmp_inmemory_utils::XmpInfo::from_source(&mut buf_reader, asset_type)
   2978                 .provenance
   2979         {
   2980             // make sure it parses
   2981             let _u = url::Url::parse(&ext_ref).ok()?;
   2982             Some(ext_ref)
   2983         } else {
   2984             None
   2985         }
   2986     }
   2987 
   2988     /// check the input url to see if it is a supported remotes URI
   2989     pub fn is_valid_remote_url(url: &str) -> bool {
   2990         match url::Url::parse(url) {
   2991             Ok(u) => u.scheme() == "http" || u.scheme() == "https",
   2992             Err(_) => false,
   2993         }
   2994     }
   2995 
   2996     /// Load Store from a in-memory asset
   2997     /// asset_type: asset extension or mime type
   2998     /// data: reference to bytes of the the file
   2999     /// verify: if true will run verification checks when loading
   3000     /// validation_log: If present all found errors are logged and returned, otherwise first error causes exit and is returned
   3001     pub fn load_from_memory(
   3002         asset_type: &str,
   3003         data: &[u8],
   3004         verify: bool,
   3005         validation_log: &mut impl StatusTracker,
   3006     ) -> Result<Store> {
   3007         Store::get_store_from_memory(asset_type, data, validation_log).and_then(|store| {
   3008             // verify the store
   3009             if verify {
   3010                 // verify store and claims
   3011                 Store::verify_store(
   3012                     &store,
   3013                     &mut ClaimAssetData::Bytes(data, asset_type),
   3014                     validation_log,
   3015                 )?;
   3016             }
   3017 
   3018             Ok(store)
   3019         })
   3020     }
   3021 
   3022     /// Load Store from a in-memory asset asynchronously validating
   3023     /// asset_type: asset extension or mime type
   3024     /// data: reference to bytes of the file
   3025     /// verify: if true will run verification checks when loading
   3026     /// validation_log: If present all found errors are logged and returned, otherwise first error causes exit and is returned
   3027     pub async fn load_from_memory_async(
   3028         asset_type: &str,
   3029         data: &[u8],
   3030         verify: bool,
   3031         validation_log: &mut impl StatusTracker,
   3032     ) -> Result<Store> {
   3033         let store = Store::get_store_from_memory(asset_type, data, validation_log)?;
   3034 
   3035         // verify the store
   3036         if verify {
   3037             // verify store and claims
   3038             Store::verify_store_async(
   3039                 &store,
   3040                 &mut ClaimAssetData::Bytes(data, asset_type),
   3041                 validation_log,
   3042             )
   3043             .await?;
   3044         }
   3045 
   3046         Ok(store)
   3047     }
   3048 
   3049     /// Load Store from a in-memory asset
   3050     /// asset_type: asset extension or mime type
   3051     /// data: reference to bytes of the the file
   3052     /// verify: if true will run verification checks when loading
   3053     /// validation_log: If present all found errors are logged and returned, otherwise first error causes exit and is returned
   3054     pub fn load_fragment_from_memory(
   3055         asset_type: &str,
   3056         init_segment: &[u8],
   3057         fragment: &[u8],
   3058         verify: bool,
   3059         validation_log: &mut impl StatusTracker,
   3060     ) -> Result<Store> {
   3061         Store::get_store_from_memory(asset_type, init_segment, validation_log).and_then(|store| {
   3062             // verify the store
   3063             if verify {
   3064                 let mut init_segment_stream = Cursor::new(init_segment);
   3065                 let mut fragment_stream = Cursor::new(fragment);
   3066 
   3067                 // verify store and claims
   3068                 Store::verify_store(
   3069                     &store,
   3070                     &mut ClaimAssetData::StreamFragment(
   3071                         &mut init_segment_stream,
   3072                         &mut fragment_stream,
   3073                         asset_type,
   3074                     ),
   3075                     validation_log,
   3076                 )?;
   3077             }
   3078 
   3079             Ok(store)
   3080         })
   3081     }
   3082 
   3083     /// Load Store from a in-memory asset asynchronously validating
   3084     /// asset_type: asset extension or mime type
   3085     /// init_segment: reference to bytes of the init segment
   3086     /// fragment: reference to bytes of the fragment to validate
   3087     /// verify: if true will run verification checks when loading
   3088     /// validation_log: If present all found errors are logged and returned, otherwise first error causes exit and is returned
   3089     pub async fn load_fragment_from_memory_async(
   3090         asset_type: &str,
   3091         init_segment: &[u8],
   3092         fragment: &[u8],
   3093         verify: bool,
   3094         validation_log: &mut impl StatusTracker,
   3095     ) -> Result<Store> {
   3096         let store = Store::get_store_from_memory(asset_type, init_segment, validation_log)?;
   3097 
   3098         // verify the store
   3099         if verify {
   3100             let mut init_segment_stream = Cursor::new(init_segment);
   3101             let mut fragment_stream = Cursor::new(fragment);
   3102 
   3103             // verify store and claims
   3104             Store::verify_store_async(
   3105                 &store,
   3106                 &mut ClaimAssetData::StreamFragment(
   3107                     &mut init_segment_stream,
   3108                     &mut fragment_stream,
   3109                     asset_type,
   3110                 ),
   3111                 validation_log,
   3112             )
   3113             .await?;
   3114         }
   3115 
   3116         Ok(store)
   3117     }
   3118 
   3119     /// Load Store from memory and add its content as a claim ingredient
   3120     /// claim: claim to add an ingredient
   3121     /// provenance_label: label of the provenance claim used as key into ingredient map
   3122     /// data: jumbf data block
   3123     pub fn load_ingredient_to_claim(
   3124         claim: &mut Claim,
   3125         provenance_label: &str,
   3126         data: &[u8],
   3127         redactions: Option<Vec<String>>,
   3128     ) -> Result<Store> {
   3129         let mut report = OneShotStatusTracker::new();
   3130         let store = Store::from_jumbf(data, &mut report)?;
   3131         claim.add_ingredient_data(provenance_label, store.claims.clone(), redactions)?;
   3132         Ok(store)
   3133     }
   3134 }
   3135 
   3136 impl std::fmt::Display for Store {
   3137     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
   3138         let report = &ManifestStoreReport::from_store(self).unwrap_or_default();
   3139         f.write_str(&format!("{}", &report))
   3140     }
   3141 }
   3142 
   3143 /// `InvalidClaimError` provides additional detail on error cases for [`Store::from_jumbf`].
   3144 #[derive(Debug, thiserror::Error)]
   3145 pub enum InvalidClaimError {
   3146     /// The "c2pa" block was not found in the asset.
   3147     #[error("\"c2pa\" block not found")]
   3148     C2paBlockNotFound,
   3149 
   3150     #[error("\"c2pa\" multiple claim boxes found in manifest")]
   3151     C2paMultipleClaimBoxes,
   3152 
   3153     /// The claim superbox was not found.
   3154     #[error("claim superbox not found")]
   3155     ClaimSuperboxNotFound,
   3156 
   3157     /// The claim description box was not found.
   3158     #[error("claim description box not found")]
   3159     ClaimDescriptionBoxNotFound,
   3160 
   3161     /// More than one claim description box was found.
   3162     #[error("more than one claim description box was found for {label}")]
   3163     DuplicateClaimBox { label: String },
   3164 
   3165     /// The expected data not found in claim box.
   3166     #[error("claim cbor box not valid")]
   3167     ClaimBoxData,
   3168 
   3169     /// The claim has a version that is newer than supported by this crate.
   3170     #[error("claim version is too new, not supported")]
   3171     ClaimVersionTooNew,
   3172 
   3173     /// The claim description box could not be parsed.
   3174     #[error("claim description box was invalid")]
   3175     ClaimDescriptionBoxInvalid,
   3176 
   3177     /// The claim signature box was not found.
   3178     #[error("claim signature box was not found")]
   3179     ClaimSignatureBoxNotFound,
   3180 
   3181     /// The claim signature description box was not found.
   3182     #[error("claim signature description box was not found")]
   3183     ClaimSignatureDescriptionBoxNotFound,
   3184 
   3185     /// The claim signature description box was invalid.
   3186     #[error("claim signature description box was invalid")]
   3187     ClaimSignatureDescriptionBoxInvalid,
   3188 
   3189     /// The assertion store superbox was not found.
   3190     #[error("assertion store superbox not found")]
   3191     AssertionStoreSuperboxNotFound,
   3192 
   3193     /// The verifiable credentials store could not be read.
   3194     #[error("the verifiable credentials store could not be read")]
   3195     VerifiableCredentialStoreInvalid,
   3196 
   3197     /// The assertion store does not contain the expected number of assertions.
   3198     #[error(
   3199         "unexpected number of assertions in assertion store (expected {expected}, found {found})"
   3200     )]
   3201     AssertionCountMismatch { expected: usize, found: usize },
   3202 }
   3203 
   3204 #[cfg(test)]
   3205 #[cfg(feature = "file_io")]
   3206 pub mod tests {
   3207     #![allow(clippy::expect_used)]
   3208     #![allow(clippy::panic)]
   3209     #![allow(clippy::unwrap_used)]
   3210 
   3211     use std::io::Write;
   3212 
   3213     use memchr::memmem;
   3214     use sha2::{Digest, Sha256};
   3215     use tempfile::tempdir;
   3216 
   3217     use super::*;
   3218     use crate::{
   3219         assertion::AssertionJson,
   3220         assertions::{labels::BOX_HASH, Action, Actions, BoxHash, Uuid},
   3221         claim::AssertionStoreJsonFormat,
   3222         jumbf_io::{get_assetio_handler_from_path, update_file_jumbf},
   3223         status_tracker::*,
   3224         utils::{
   3225             hash_utils::Hasher,
   3226             patch::patch_file,
   3227             test::{
   3228                 create_test_claim, fixture_path, temp_dir_path, temp_fixture_path, temp_signer,
   3229                 write_jpeg_placeholder_file,
   3230             },
   3231         },
   3232         SigningAlg,
   3233     };
   3234 
   3235     fn create_editing_claim(claim: &mut Claim) -> Result<&mut Claim> {
   3236         let uuid_str = "deadbeefdeadbeefdeadbeefdeadbeef";
   3237 
   3238         // add a binary thumbnail assertion  ('deadbeefadbeadbe')
   3239         let some_binary_data: Vec<u8> = vec![
   3240             0x0d, 0x0e, 0x0a, 0x0d, 0x0b, 0x0e, 0x0e, 0x0f, 0x0a, 0x0d, 0x0b, 0x0e, 0x0a, 0x0d,
   3241             0x0b, 0x0e,
   3242         ];
   3243 
   3244         let uuid_assertion = Uuid::new("test uuid", uuid_str.to_string(), some_binary_data);
   3245 
   3246         claim.add_assertion(&uuid_assertion)?;
   3247 
   3248         Ok(claim)
   3249     }
   3250 
   3251     fn create_capture_claim(claim: &mut Claim) -> Result<&mut Claim> {
   3252         let actions = Actions::new().add_action(Action::new("c2pa.created"));
   3253 
   3254         claim.add_assertion(&actions)?;
   3255 
   3256         Ok(claim)
   3257     }
   3258 
   3259     #[test]
   3260     #[cfg(feature = "file_io")]
   3261     fn test_jumbf_generation() {
   3262         // test adding to actual image
   3263         let ap = fixture_path("earth_apollo17.jpg");
   3264         let temp_dir = tempdir().expect("temp dir");
   3265         let op = temp_dir_path(&temp_dir, "test-image.jpg");
   3266 
   3267         // Create claims store.
   3268         let mut store = Store::new();
   3269 
   3270         // Create a new claim.
   3271         let claim1 = create_test_claim().unwrap();
   3272 
   3273         // Create a new claim.
   3274         let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
   3275         create_editing_claim(&mut claim2).unwrap();
   3276 
   3277         // Create a 3rd party claim
   3278         let mut claim_capture = Claim::new("capture", Some("claim_capture"));
   3279         create_capture_claim(&mut claim_capture).unwrap();
   3280 
   3281         // Do we generate JUMBF?
   3282         let signer = temp_signer();
   3283 
   3284         // Test generate JUMBF
   3285         // Get labels for label test
   3286         let claim1_label = claim1.label().to_string();
   3287         let capture = claim_capture.label().to_string();
   3288         let claim2_label = claim2.label().to_string();
   3289 
   3290         // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
   3291         store.commit_claim(claim1).unwrap();
   3292         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   3293         store.commit_claim(claim_capture).unwrap();
   3294         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3295         store.commit_claim(claim2).unwrap();
   3296         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3297 
   3298         // test finding claims by label
   3299         let c1 = store.get_claim(&claim1_label);
   3300         let c2 = store.get_claim(&capture);
   3301         let c3 = store.get_claim(&claim2_label);
   3302         assert_eq!(&claim1_label, c1.unwrap().label());
   3303         assert_eq!(&capture, c2.unwrap().label());
   3304         assert_eq!(claim2_label, c3.unwrap().label());
   3305 
   3306         // write to new file
   3307         println!("Provenance: {}\n", store.provenance_path().unwrap());
   3308 
   3309         // read from new file
   3310         let new_store =
   3311             Store::load_from_asset(&op, true, &mut OneShotStatusTracker::new()).unwrap();
   3312 
   3313         // can  we get by the ingredient data back
   3314         let _some_binary_data: Vec<u8> = vec![
   3315             0x0d, 0x0e, 0x0a, 0x0d, 0x0b, 0x0e, 0x0e, 0x0f, 0x0a, 0x0d, 0x0b, 0x0e, 0x0a, 0x0d,
   3316             0x0b, 0x0e,
   3317         ];
   3318 
   3319         // dump store and compare to original
   3320         for claim in new_store.claims() {
   3321             let _restored_json = claim
   3322                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3323                 .unwrap();
   3324             let _orig_json = store
   3325                 .get_claim(claim.label())
   3326                 .unwrap()
   3327                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3328                 .unwrap();
   3329 
   3330             // these better match
   3331             //assert_eq!(orig_json, restored_json);
   3332             //assert_eq!(claim.hash(), store.claims()[idx].hash());
   3333 
   3334             println!(
   3335                 "Claim: {} \n{}",
   3336                 claim.label(),
   3337                 claim
   3338                     .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   3339                     .expect("could not restore from json")
   3340             );
   3341 
   3342             for hashed_uri in claim.assertions() {
   3343                 let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   3344                 claim.get_claim_assertion(&label, instance).unwrap();
   3345             }
   3346         }
   3347 
   3348         // test patch file - bytes should be same so error should not be detected
   3349         let mut splice_point =
   3350             patch_file(&op, "thumbnail".as_bytes(), "testme".as_bytes()).unwrap();
   3351 
   3352         let mut restore_point =
   3353             patch_file(&op, "testme".as_bytes(), "thumbnail".as_bytes()).unwrap();
   3354 
   3355         assert_eq!(splice_point, restore_point);
   3356 
   3357         Store::load_from_asset(&op, true, &mut OneShotStatusTracker::new())
   3358             .expect("Should still verify");
   3359 
   3360         // test patching jumbf - error should be detected
   3361 
   3362         splice_point = update_file_jumbf(&op, "thumbnail".as_bytes(), "testme".as_bytes()).unwrap();
   3363         restore_point =
   3364             update_file_jumbf(&op, "testme".as_bytes(), "thumbnail.v1".as_bytes()).unwrap();
   3365 
   3366         assert_eq!(splice_point, restore_point);
   3367 
   3368         Store::load_from_asset(&op, true, &mut OneShotStatusTracker::new())
   3369             .expect_err("Should not verify");
   3370     }
   3371 
   3372     #[test]
   3373     #[cfg(feature = "file_io")]
   3374     fn test_unknown_asset_type_generation() {
   3375         // test adding to actual image
   3376         let ap = fixture_path("unsupported_type.txt");
   3377         let temp_dir = tempdir().expect("temp dir");
   3378         let op = temp_dir_path(&temp_dir, "unsupported_type.txt");
   3379 
   3380         // Create claims store.
   3381         let mut store = Store::new();
   3382 
   3383         // Create a new claim.
   3384         let claim1 = create_test_claim().unwrap();
   3385 
   3386         // Create a new claim.
   3387         let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
   3388         create_editing_claim(&mut claim2).unwrap();
   3389 
   3390         // Create a 3rd party claim
   3391         let mut claim_capture = Claim::new("capture", Some("claim_capture"));
   3392         create_capture_claim(&mut claim_capture).unwrap();
   3393 
   3394         // Do we generate JUMBF?
   3395         let signer = temp_signer();
   3396 
   3397         // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
   3398         store.commit_claim(claim1).unwrap();
   3399         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   3400 
   3401         // read from new file
   3402         let new_store =
   3403             Store::load_from_asset(&op, true, &mut OneShotStatusTracker::new()).unwrap();
   3404 
   3405         // can  we get by the ingredient data back
   3406 
   3407         // dump store and compare to original
   3408         for claim in new_store.claims() {
   3409             let _restored_json = claim
   3410                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3411                 .unwrap();
   3412             let _orig_json = store
   3413                 .get_claim(claim.label())
   3414                 .unwrap()
   3415                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3416                 .unwrap();
   3417 
   3418             println!(
   3419                 "Claim: {} \n{}",
   3420                 claim.label(),
   3421                 claim
   3422                     .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   3423                     .expect("could not restore from json")
   3424             );
   3425 
   3426             for hashed_uri in claim.assertions() {
   3427                 let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   3428                 claim.get_claim_assertion(&label, instance).unwrap();
   3429             }
   3430         }
   3431     }
   3432 
   3433     struct BadSigner {}
   3434 
   3435     impl crate::Signer for BadSigner {
   3436         fn sign(&self, _data: &[u8]) -> Result<Vec<u8>> {
   3437             Ok(b"not a valid signature".to_vec())
   3438         }
   3439 
   3440         fn alg(&self) -> SigningAlg {
   3441             SigningAlg::Ps256
   3442         }
   3443 
   3444         fn certs(&self) -> Result<Vec<Vec<u8>>> {
   3445             Ok(Vec::new())
   3446         }
   3447 
   3448         fn reserve_size(&self) -> usize {
   3449             42
   3450         }
   3451     }
   3452 
   3453     #[test]
   3454     #[cfg(feature = "file_io")]
   3455     fn test_detects_unverifiable_signature() {
   3456         // test adding to actual image
   3457         let ap = fixture_path("earth_apollo17.jpg");
   3458         let temp_dir = tempdir().expect("temp dir");
   3459         let op = temp_dir_path(&temp_dir, "test-image-unverified.jpg");
   3460 
   3461         let mut store = Store::new();
   3462 
   3463         let claim = create_test_claim().unwrap();
   3464 
   3465         let signer = BadSigner {};
   3466 
   3467         // JUMBF generation should fail because this signature won't validate.
   3468         store.commit_claim(claim).unwrap();
   3469 
   3470         // TO DO: This generates a log spew when running this test.
   3471         // I don't have time to fix this right now.
   3472         // [(date) ERROR c2pa::store] Signature that was just generated does not validate: CoseCbor
   3473 
   3474         store.save_to_asset(&ap, &signer, &op).unwrap_err();
   3475     }
   3476 
   3477     #[test]
   3478     #[cfg(feature = "file_io")]
   3479     fn test_sign_with_expired_cert() {
   3480         use crate::{openssl::RsaSigner, signer::ConfigurableSigner, SigningAlg};
   3481 
   3482         // test adding to actual image
   3483         let ap = fixture_path("earth_apollo17.jpg");
   3484         let temp_dir = tempdir().expect("temp dir");
   3485         let op = temp_dir_path(&temp_dir, "test-image-expired-cert.jpg");
   3486 
   3487         let mut store = Store::new();
   3488 
   3489         let claim = create_test_claim().unwrap();
   3490 
   3491         let signcert_path = fixture_path("rsa-pss256_key-expired.pub");
   3492         let pkey_path = fixture_path("rsa-pss256-expired.pem");
   3493         let signer =
   3494             RsaSigner::from_files(signcert_path, pkey_path, SigningAlg::Ps256, None).unwrap();
   3495 
   3496         store.commit_claim(claim).unwrap();
   3497 
   3498         let r = store.save_to_asset(&ap, &signer, &op);
   3499         assert!(r.is_err());
   3500         assert_eq!(r.err().unwrap().to_string(), "COSE certificate has expired");
   3501     }
   3502 
   3503     #[test]
   3504     #[cfg(feature = "file_io")]
   3505     fn test_jumbf_replacement_generation() {
   3506         // Create claims store.
   3507         let mut store = Store::new();
   3508 
   3509         // Create a new claim.
   3510         let claim1 = create_test_claim().unwrap();
   3511         store.commit_claim(claim1).unwrap();
   3512 
   3513         // do we generate JUMBF
   3514         let jumbf_bytes = store.to_jumbf_internal(512).unwrap();
   3515         assert!(!jumbf_bytes.is_empty());
   3516 
   3517         // test adding to actual image
   3518         let ap = fixture_path("prerelease.jpg");
   3519         let temp_dir = tempdir().expect("temp dir");
   3520         let op = temp_dir_path(&temp_dir, "replacement_test.jpg");
   3521 
   3522         // grab jumbf from original
   3523         let original_jumbf = load_jumbf_from_file(&ap).unwrap();
   3524 
   3525         // replace with new jumbf
   3526         save_jumbf_to_file(&jumbf_bytes, &ap, Some(&op)).unwrap();
   3527 
   3528         let saved_jumbf = load_jumbf_from_file(&op).unwrap();
   3529 
   3530         // saved data should be the new data
   3531         assert_eq!(&jumbf_bytes, &saved_jumbf);
   3532 
   3533         // original data should not be in file anymore check for first 1k
   3534         let buf = fs::read(&op).unwrap();
   3535         assert!(memmem::find(&buf, &original_jumbf[0..1024]).is_none());
   3536     }
   3537 
   3538     #[actix::test]
   3539     async fn test_jumbf_generation_async() {
   3540         let signer = crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256);
   3541 
   3542         // test adding to actual image
   3543         let ap = fixture_path("earth_apollo17.jpg");
   3544         let temp_dir = tempdir().expect("temp dir");
   3545         let op = temp_dir_path(&temp_dir, "test-async.jpg");
   3546 
   3547         // Create claims store.
   3548         let mut store = Store::new();
   3549 
   3550         // Create a new claim.
   3551         let claim1 = create_test_claim().unwrap();
   3552 
   3553         // Create a new claim.
   3554         let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
   3555         create_editing_claim(&mut claim2).unwrap();
   3556 
   3557         // Create a 3rd party claim
   3558         let mut claim_capture = Claim::new("capture", Some("claim_capture"));
   3559         create_capture_claim(&mut claim_capture).unwrap();
   3560 
   3561         // Test generate JUMBF
   3562         // Get labels for label test
   3563         let claim1_label = claim1.label().to_string();
   3564         let capture = claim_capture.label().to_string();
   3565         let claim2_label = claim2.label().to_string();
   3566 
   3567         store.commit_claim(claim1).unwrap();
   3568         store.save_to_asset_async(&ap, &signer, &op).await.unwrap();
   3569         store.commit_claim(claim_capture).unwrap();
   3570         store.save_to_asset_async(&ap, &signer, &op).await.unwrap();
   3571         store.commit_claim(claim2).unwrap();
   3572         store.save_to_asset_async(&ap, &signer, &op).await.unwrap();
   3573 
   3574         // test finding claims by label
   3575         let c1 = store.get_claim(&claim1_label);
   3576         let c2 = store.get_claim(&capture);
   3577         let c3 = store.get_claim(&claim2_label);
   3578         assert_eq!(&claim1_label, c1.unwrap().label());
   3579         assert_eq!(&capture, c2.unwrap().label());
   3580         assert_eq!(claim2_label, c3.unwrap().label());
   3581 
   3582         // Do we generate JUMBF
   3583         let jumbf_bytes = store.to_jumbf_async(&signer).unwrap();
   3584         assert!(!jumbf_bytes.is_empty());
   3585 
   3586         // write to new file
   3587         println!("Provenance: {}\n", store.provenance_path().unwrap());
   3588 
   3589         // make sure we can read from new file
   3590         let mut report = DetailedStatusTracker::new();
   3591         let _new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   3592     }
   3593 
   3594     #[actix::test]
   3595     async fn test_jumbf_generation_remote() {
   3596         // test adding to actual image
   3597         let ap = fixture_path("earth_apollo17.jpg");
   3598         let temp_dir = tempdir().expect("temp dir");
   3599         let op = temp_dir_path(&temp_dir, "test-async.jpg");
   3600 
   3601         // Create claims store.
   3602         let mut store = Store::new();
   3603 
   3604         // Create a new claim.
   3605         let claim1 = create_test_claim().unwrap();
   3606 
   3607         // create my remote signer to map the CoseSign1 data back into the asset
   3608         let remote_signer = crate::utils::test::temp_remote_signer();
   3609 
   3610         store.commit_claim(claim1).unwrap();
   3611         store
   3612             .save_to_asset_remote_signed(&ap, remote_signer.as_ref(), &op)
   3613             .await
   3614             .unwrap();
   3615 
   3616         // make sure we can read from new file
   3617         let mut report = DetailedStatusTracker::new();
   3618         let _new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   3619     }
   3620 
   3621     #[test]
   3622     #[cfg(feature = "file_io")]
   3623     fn test_png_jumbf_generation() {
   3624         // test adding to actual image
   3625         let ap = fixture_path("libpng-test.png");
   3626         let temp_dir = tempdir().expect("temp dir");
   3627         let op = temp_dir_path(&temp_dir, "libpng-test-c2pa.png");
   3628 
   3629         // Create claims store.
   3630         let mut store = Store::new();
   3631 
   3632         // Create a new claim.
   3633         let claim1 = create_test_claim().unwrap();
   3634 
   3635         // Create a new claim.
   3636         let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
   3637         create_editing_claim(&mut claim2).unwrap();
   3638 
   3639         // Create a 3rd party claim
   3640         let mut claim_capture = Claim::new("capture", Some("claim_capture"));
   3641         create_capture_claim(&mut claim_capture).unwrap();
   3642 
   3643         // Do we generate JUMBF?
   3644         let signer = temp_signer();
   3645 
   3646         // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
   3647         store.commit_claim(claim1).unwrap();
   3648         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   3649         store.commit_claim(claim_capture).unwrap();
   3650         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3651         store.commit_claim(claim2).unwrap();
   3652         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3653 
   3654         // write to new file
   3655         println!("Provenance: {}\n", store.provenance_path().unwrap());
   3656 
   3657         let mut report = DetailedStatusTracker::new();
   3658 
   3659         // read from new file
   3660         let new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   3661 
   3662         // can  we get by the ingredient data back
   3663         let _some_binary_data: Vec<u8> = vec![
   3664             0x0d, 0x0e, 0x0a, 0x0d, 0x0b, 0x0e, 0x0e, 0x0f, 0x0a, 0x0d, 0x0b, 0x0e, 0x0a, 0x0d,
   3665             0x0b, 0x0e,
   3666         ];
   3667 
   3668         // dump store and compare to original
   3669         for claim in new_store.claims() {
   3670             let _restored_json = claim
   3671                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3672                 .unwrap();
   3673             let _orig_json = store
   3674                 .get_claim(claim.label())
   3675                 .unwrap()
   3676                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3677                 .unwrap();
   3678 
   3679             println!(
   3680                 "Claim: {} \n{}",
   3681                 claim.label(),
   3682                 claim
   3683                     .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   3684                     .expect("could not restore from json")
   3685             );
   3686 
   3687             for hashed_uri in claim.assertions() {
   3688                 let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   3689                 claim
   3690                     .get_claim_assertion(&label, instance)
   3691                     .expect("Should find assertion");
   3692             }
   3693         }
   3694     }
   3695 
   3696     #[test]
   3697     #[cfg(feature = "file_io")]
   3698     fn test_get_data_boxes() {
   3699         // Create a new claim.
   3700         use crate::jumbf::labels::to_relative_uri;
   3701         let claim1 = create_test_claim().unwrap();
   3702 
   3703         for (uri, db) in claim1.databoxes() {
   3704             // test full path
   3705             assert!(claim1.get_data_box(&uri.url()).is_some());
   3706 
   3707             // test with relative path
   3708             let rel_path = to_relative_uri(&uri.url());
   3709             assert!(claim1.get_data_box(&rel_path).is_some());
   3710 
   3711             // test values
   3712             assert_eq!(db, claim1.get_data_box(&uri.url()).unwrap());
   3713         }
   3714     }
   3715 
   3716     /*  reenable this test once we place for large test files
   3717         #[test]
   3718         #[cfg(feature = "file_io")]
   3719         fn test_arw_jumbf_generation() {
   3720             let ap = fixture_path("sample1.arw");
   3721             let temp_dir = tempdir().expect("temp dir");
   3722             let op = temp_dir_path(&temp_dir, "ssample1.arw");
   3723 
   3724             // Create claims store.
   3725             let mut store = Store::new();
   3726 
   3727             // Create a new claim.
   3728             let claim1 = create_test_claim().unwrap();
   3729 
   3730             // Create a new claim.
   3731             let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
   3732             create_editing_claim(&mut claim2).unwrap();
   3733 
   3734             // Create a 3rd party claim
   3735             let mut claim_capture = Claim::new("capture", Some("claim_capture"));
   3736             create_capture_claim(&mut claim_capture).unwrap();
   3737 
   3738             // Do we generate JUMBF?
   3739             let signer = temp_signer();
   3740 
   3741             // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commmits
   3742             store.commit_claim(claim1).unwrap();
   3743             store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   3744             store.commit_claim(claim_capture).unwrap();
   3745             store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3746             store.commit_claim(claim2).unwrap();
   3747             store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3748 
   3749             // write to new file
   3750             println!("Provenance: {}\n", store.provenance_path().unwrap());
   3751 
   3752             let mut report = DetailedStatusTracker::new();
   3753 
   3754             // read from new file
   3755             let new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   3756 
   3757             // can  we get by the ingredient data back
   3758             let _some_binary_data: Vec<u8> = vec![
   3759                 0x0d, 0x0e, 0x0a, 0x0d, 0x0b, 0x0e, 0x0e, 0x0f, 0x0a, 0x0d, 0x0b, 0x0e, 0x0a, 0x0d,
   3760                 0x0b, 0x0e,
   3761             ];
   3762 
   3763             // dump store and compare to original
   3764             for claim in new_store.claims() {
   3765                 let _restored_json = claim
   3766                     .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3767                     .unwrap();
   3768                 let _orig_json = store
   3769                     .get_claim(claim.label())
   3770                     .unwrap()
   3771                     .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3772                     .unwrap();
   3773 
   3774                 println!(
   3775                     "Claim: {} \n{}",
   3776                     claim.label(),
   3777                     claim
   3778                         .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   3779                         .expect("could not restore from json")
   3780                 );
   3781 
   3782                 for hashed_uri in claim.assertions() {
   3783                     let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   3784                     claim
   3785                         .get_claim_assertion(&label, instance)
   3786                         .expect("Should find assertion");
   3787                 }
   3788             }
   3789         }
   3790         #[test]
   3791         #[cfg(feature = "file_io")]
   3792         fn test_nef_jumbf_generation() {
   3793             let ap = fixture_path("sample1.nef");
   3794             let temp_dir = tempdir().expect("temp dir");
   3795             let op = temp_dir_path(&temp_dir, "ssample1.nef");
   3796 
   3797             // Create claims store.
   3798             let mut store = Store::new();
   3799 
   3800             // Create a new claim.
   3801             let claim1 = create_test_claim().unwrap();
   3802 
   3803             // Create a new claim.
   3804             let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
   3805             create_editing_claim(&mut claim2).unwrap();
   3806 
   3807             // Create a 3rd party claim
   3808             let mut claim_capture = Claim::new("capture", Some("claim_capture"));
   3809             create_capture_claim(&mut claim_capture).unwrap();
   3810 
   3811             // Do we generate JUMBF?
   3812             let signer = temp_signer();
   3813 
   3814             // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commmits
   3815             store.commit_claim(claim1).unwrap();
   3816             store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   3817             store.commit_claim(claim_capture).unwrap();
   3818             store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3819             store.commit_claim(claim2).unwrap();
   3820             store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3821 
   3822             // write to new file
   3823             println!("Provenance: {}\n", store.provenance_path().unwrap());
   3824 
   3825             let mut report = DetailedStatusTracker::new();
   3826 
   3827             // read from new file
   3828             let new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   3829 
   3830             // can  we get by the ingredient data back
   3831             let _some_binary_data: Vec<u8> = vec![
   3832                 0x0d, 0x0e, 0x0a, 0x0d, 0x0b, 0x0e, 0x0e, 0x0f, 0x0a, 0x0d, 0x0b, 0x0e, 0x0a, 0x0d,
   3833                 0x0b, 0x0e,
   3834             ];
   3835 
   3836             // dump store and compare to original
   3837             for claim in new_store.claims() {
   3838                 let _restored_json = claim
   3839                     .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3840                     .unwrap();
   3841                 let _orig_json = store
   3842                     .get_claim(claim.label())
   3843                     .unwrap()
   3844                     .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3845                     .unwrap();
   3846 
   3847                 println!(
   3848                     "Claim: {} \n{}",
   3849                     claim.label(),
   3850                     claim
   3851                         .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   3852                         .expect("could not restore from json")
   3853                 );
   3854 
   3855                 for hashed_uri in claim.assertions() {
   3856                     let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   3857                     claim
   3858                         .get_claim_assertion(&label, instance)
   3859                         .expect("Should find assertion");
   3860                 }
   3861             }
   3862         }
   3863     */
   3864     #[test]
   3865     #[cfg(feature = "file_io")]
   3866     fn test_wav_jumbf_generation() {
   3867         let ap = fixture_path("sample1.wav");
   3868         let temp_dir = tempdir().expect("temp dir");
   3869         let op = temp_dir_path(&temp_dir, "ssample1.wav");
   3870 
   3871         // Create claims store.
   3872         let mut store = Store::new();
   3873 
   3874         // Create a new claim.
   3875         let claim1 = create_test_claim().unwrap();
   3876 
   3877         // Create a new claim.
   3878         let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
   3879         create_editing_claim(&mut claim2).unwrap();
   3880 
   3881         // Create a 3rd party claim
   3882         let mut claim_capture = Claim::new("capture", Some("claim_capture"));
   3883         create_capture_claim(&mut claim_capture).unwrap();
   3884 
   3885         // Do we generate JUMBF?
   3886         let signer = temp_signer();
   3887 
   3888         // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
   3889         store.commit_claim(claim1).unwrap();
   3890         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   3891         store.commit_claim(claim_capture).unwrap();
   3892         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3893         store.commit_claim(claim2).unwrap();
   3894         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3895 
   3896         // write to new file
   3897         println!("Provenance: {}\n", store.provenance_path().unwrap());
   3898 
   3899         let mut report = DetailedStatusTracker::new();
   3900 
   3901         // read from new file
   3902         let new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   3903 
   3904         // can  we get by the ingredient data back
   3905         let _some_binary_data: Vec<u8> = vec![
   3906             0x0d, 0x0e, 0x0a, 0x0d, 0x0b, 0x0e, 0x0e, 0x0f, 0x0a, 0x0d, 0x0b, 0x0e, 0x0a, 0x0d,
   3907             0x0b, 0x0e,
   3908         ];
   3909 
   3910         // dump store and compare to original
   3911         for claim in new_store.claims() {
   3912             let _restored_json = claim
   3913                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3914                 .unwrap();
   3915             let _orig_json = store
   3916                 .get_claim(claim.label())
   3917                 .unwrap()
   3918                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3919                 .unwrap();
   3920 
   3921             println!(
   3922                 "Claim: {} \n{}",
   3923                 claim.label(),
   3924                 claim
   3925                     .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   3926                     .expect("could not restore from json")
   3927             );
   3928 
   3929             for hashed_uri in claim.assertions() {
   3930                 let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   3931                 claim
   3932                     .get_claim_assertion(&label, instance)
   3933                     .expect("Should find assertion");
   3934             }
   3935         }
   3936     }
   3937 
   3938     #[test]
   3939     #[cfg(feature = "file_io")]
   3940     fn test_avi_jumbf_generation() {
   3941         let ap = fixture_path("test.avi");
   3942         let temp_dir = tempdir().expect("temp dir");
   3943         let op = temp_dir_path(&temp_dir, "test.avi");
   3944 
   3945         // Create claims store.
   3946         let mut store = Store::new();
   3947 
   3948         // Create a new claim.
   3949         let claim1 = create_test_claim().unwrap();
   3950 
   3951         // Create a new claim.
   3952         let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
   3953         create_editing_claim(&mut claim2).unwrap();
   3954 
   3955         // Create a 3rd party claim
   3956         let mut claim_capture = Claim::new("capture", Some("claim_capture"));
   3957         create_capture_claim(&mut claim_capture).unwrap();
   3958 
   3959         // Do we generate JUMBF?
   3960         let signer = temp_signer();
   3961 
   3962         // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
   3963         store.commit_claim(claim1).unwrap();
   3964         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   3965         store.commit_claim(claim_capture).unwrap();
   3966         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3967         store.commit_claim(claim2).unwrap();
   3968         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   3969 
   3970         // write to new file
   3971         println!("Provenance: {}\n", store.provenance_path().unwrap());
   3972 
   3973         let mut report = DetailedStatusTracker::new();
   3974 
   3975         // read from new file
   3976         let new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   3977 
   3978         // can  we get by the ingredient data back
   3979         let _some_binary_data: Vec<u8> = vec![
   3980             0x0d, 0x0e, 0x0a, 0x0d, 0x0b, 0x0e, 0x0e, 0x0f, 0x0a, 0x0d, 0x0b, 0x0e, 0x0a, 0x0d,
   3981             0x0b, 0x0e,
   3982         ];
   3983 
   3984         // dump store and compare to original
   3985         for claim in new_store.claims() {
   3986             let _restored_json = claim
   3987                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3988                 .unwrap();
   3989             let _orig_json = store
   3990                 .get_claim(claim.label())
   3991                 .unwrap()
   3992                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   3993                 .unwrap();
   3994 
   3995             println!(
   3996                 "Claim: {} \n{}",
   3997                 claim.label(),
   3998                 claim
   3999                     .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   4000                     .expect("could not restore from json")
   4001             );
   4002 
   4003             for hashed_uri in claim.assertions() {
   4004                 let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   4005                 claim
   4006                     .get_claim_assertion(&label, instance)
   4007                     .expect("Should find assertion");
   4008             }
   4009         }
   4010     }
   4011 
   4012     #[test]
   4013     #[cfg(feature = "file_io")]
   4014     fn test_webp_jumbf_generation() {
   4015         let ap = fixture_path("sample1.webp");
   4016         let temp_dir = tempdir().expect("temp dir");
   4017         let op = temp_dir_path(&temp_dir, "sample1.webp");
   4018 
   4019         // Create claims store.
   4020         let mut store = Store::new();
   4021 
   4022         // Create a new claim.
   4023         let claim1 = create_test_claim().unwrap();
   4024 
   4025         // Create a new claim.
   4026         let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
   4027         create_editing_claim(&mut claim2).unwrap();
   4028 
   4029         // Create a 3rd party claim
   4030         let mut claim_capture = Claim::new("capture", Some("claim_capture"));
   4031         create_capture_claim(&mut claim_capture).unwrap();
   4032 
   4033         // Do we generate JUMBF?
   4034         let signer = temp_signer();
   4035 
   4036         // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
   4037         store.commit_claim(claim1).unwrap();
   4038         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   4039         store.commit_claim(claim_capture).unwrap();
   4040         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   4041         store.commit_claim(claim2).unwrap();
   4042         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   4043 
   4044         // write to new file
   4045         println!("Provenance: {}\n", store.provenance_path().unwrap());
   4046 
   4047         let mut report = DetailedStatusTracker::new();
   4048 
   4049         // read from new file
   4050         let new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   4051 
   4052         // can  we get by the ingredient data back
   4053         let _some_binary_data: Vec<u8> = vec![
   4054             0x0d, 0x0e, 0x0a, 0x0d, 0x0b, 0x0e, 0x0e, 0x0f, 0x0a, 0x0d, 0x0b, 0x0e, 0x0a, 0x0d,
   4055             0x0b, 0x0e,
   4056         ];
   4057 
   4058         // dump store and compare to original
   4059         for claim in new_store.claims() {
   4060             let _restored_json = claim
   4061                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   4062                 .unwrap();
   4063             let _orig_json = store
   4064                 .get_claim(claim.label())
   4065                 .unwrap()
   4066                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   4067                 .unwrap();
   4068 
   4069             println!(
   4070                 "Claim: {} \n{}",
   4071                 claim.label(),
   4072                 claim
   4073                     .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   4074                     .expect("could not restore from json")
   4075             );
   4076 
   4077             for hashed_uri in claim.assertions() {
   4078                 let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   4079                 claim
   4080                     .get_claim_assertion(&label, instance)
   4081                     .expect("Should find assertion");
   4082             }
   4083         }
   4084     }
   4085 
   4086     #[test]
   4087     #[cfg(feature = "file_io")]
   4088     fn test_heic() {
   4089         let ap = fixture_path("sample1.heic");
   4090         let temp_dir = tempdir().expect("temp dir");
   4091         let op = temp_dir_path(&temp_dir, "sample1.heic");
   4092 
   4093         // Create claims store.
   4094         let mut store = Store::new();
   4095 
   4096         // Create a new claim.
   4097         let claim1 = create_test_claim().unwrap();
   4098 
   4099         // Do we generate JUMBF?
   4100         let signer = temp_signer();
   4101 
   4102         // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
   4103         store.commit_claim(claim1).unwrap();
   4104         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   4105 
   4106         let mut report = DetailedStatusTracker::new();
   4107 
   4108         // read from new file
   4109         let new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   4110 
   4111         // dump store and compare to original
   4112         for claim in new_store.claims() {
   4113             println!(
   4114                 "Claim: {} \n{}",
   4115                 claim.label(),
   4116                 claim
   4117                     .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   4118                     .expect("could not restore from json")
   4119             );
   4120 
   4121             for hashed_uri in claim.assertions() {
   4122                 let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   4123                 claim
   4124                     .get_claim_assertion(&label, instance)
   4125                     .expect("Should find assertion");
   4126             }
   4127         }
   4128     }
   4129 
   4130     #[test]
   4131     #[cfg(feature = "file_io")]
   4132     fn test_avif() {
   4133         let ap = fixture_path("sample1.avif");
   4134         let temp_dir = tempdir().expect("temp dir");
   4135         let op = temp_dir_path(&temp_dir, "sample1.avif");
   4136 
   4137         // Create claims store.
   4138         let mut store = Store::new();
   4139 
   4140         // Create a new claim.
   4141         let claim1 = create_test_claim().unwrap();
   4142 
   4143         // Do we generate JUMBF?
   4144         let signer = temp_signer();
   4145 
   4146         // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
   4147         store.commit_claim(claim1).unwrap();
   4148         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   4149 
   4150         let mut report = DetailedStatusTracker::new();
   4151 
   4152         // read from new file
   4153         let new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   4154 
   4155         // dump store and compare to original
   4156         for claim in new_store.claims() {
   4157             println!(
   4158                 "Claim: {} \n{}",
   4159                 claim.label(),
   4160                 claim
   4161                     .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   4162                     .expect("could not restore from json")
   4163             );
   4164 
   4165             for hashed_uri in claim.assertions() {
   4166                 let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   4167                 claim
   4168                     .get_claim_assertion(&label, instance)
   4169                     .expect("Should find assertion");
   4170             }
   4171         }
   4172     }
   4173 
   4174     #[test]
   4175     #[cfg(feature = "file_io")]
   4176     fn test_heif() {
   4177         let ap = fixture_path("sample1.heif");
   4178         let temp_dir = tempdir().expect("temp dir");
   4179         let op = temp_dir_path(&temp_dir, "sample1.heif");
   4180 
   4181         // Create claims store.
   4182         let mut store = Store::new();
   4183 
   4184         // Create a new claim.
   4185         let claim1 = create_test_claim().unwrap();
   4186 
   4187         // Do we generate JUMBF?
   4188         let signer = temp_signer();
   4189 
   4190         // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
   4191         store.commit_claim(claim1).unwrap();
   4192         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   4193 
   4194         let mut report = DetailedStatusTracker::new();
   4195 
   4196         // read from new file
   4197         let new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   4198 
   4199         // dump store and compare to original
   4200         for claim in new_store.claims() {
   4201             println!(
   4202                 "Claim: {} \n{}",
   4203                 claim.label(),
   4204                 claim
   4205                     .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   4206                     .expect("could not restore from json")
   4207             );
   4208 
   4209             for hashed_uri in claim.assertions() {
   4210                 let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   4211                 claim
   4212                     .get_claim_assertion(&label, instance)
   4213                     .expect("Should find assertion");
   4214             }
   4215         }
   4216     }
   4217 
   4218     /*  todo: disable until we can generate a valid file with no xmp
   4219     #[test]
   4220     fn test_manifest_no_xmp() {
   4221         let ap = fixture_path("CAICAI_NO_XMP.jpg");
   4222         assert!(Store::load_from_asset(&ap, true, None).is_ok());
   4223     }
   4224     */
   4225 
   4226     #[test]
   4227     fn test_manifest_bad_sig() {
   4228         let ap = fixture_path("CE-sig-CA.jpg");
   4229         assert!(Store::load_from_asset(&ap, true, &mut OneShotStatusTracker::new()).is_err());
   4230     }
   4231 
   4232     #[test]
   4233     fn test_unsupported_type_without_external_manifest() {
   4234         let ap = fixture_path("Purple Square.psd");
   4235         let mut report = DetailedStatusTracker::new();
   4236         let result = Store::load_from_asset(&ap, true, &mut report);
   4237         assert!(matches!(result, Err(Error::UnsupportedType)));
   4238         println!("Error report for {}: {:?}", ap.display(), report);
   4239         assert!(!report.get_log().is_empty());
   4240 
   4241         assert!(report_has_err(report.get_log(), Error::UnsupportedType));
   4242     }
   4243 
   4244     #[test]
   4245     fn test_bad_jumbf() {
   4246         // test bad jumbf
   4247         let ap = fixture_path("prerelease.jpg");
   4248         let mut report = DetailedStatusTracker::new();
   4249         let _r = Store::load_from_asset(&ap, true, &mut report);
   4250 
   4251         // error report
   4252         println!("Error report for {}: {:?}", ap.display(), report);
   4253         assert!(!report.get_log().is_empty());
   4254 
   4255         assert!(report_has_err(report.get_log(), Error::PrereleaseError));
   4256     }
   4257 
   4258     #[test]
   4259     fn test_detect_byte_change() {
   4260         // test bad jumbf
   4261         let ap = fixture_path("XCA.jpg");
   4262         let mut report = DetailedStatusTracker::new();
   4263         Store::load_from_asset(&ap, true, &mut report).unwrap();
   4264 
   4265         // error report
   4266         println!("Error report for {}: {:?}", ap.display(), report);
   4267         assert!(!report.get_log().is_empty());
   4268 
   4269         let errs = report_split_errors(report.get_log_mut());
   4270         assert!(report_has_status(
   4271             &errs,
   4272             validation_status::ASSERTION_DATAHASH_MISMATCH
   4273         ));
   4274     }
   4275 
   4276     #[test]
   4277     #[cfg(feature = "file_io")]
   4278     fn test_file_not_found() {
   4279         let ap = fixture_path("this_does_not_exist.jpg");
   4280         let mut report = DetailedStatusTracker::new();
   4281         let _result = Store::load_from_asset(&ap, true, &mut report);
   4282 
   4283         println!("Error report for {}: {:?}", ap.display(), report.get_log());
   4284         assert!(!report.get_log().is_empty());
   4285         let errors = report_split_errors(report.get_log_mut());
   4286         assert!(errors[0].error_str().unwrap().starts_with("IoError"));
   4287     }
   4288 
   4289     #[test]
   4290     fn test_old_manifest() {
   4291         let ap = fixture_path("prerelease.jpg");
   4292         let mut report = DetailedStatusTracker::new();
   4293         let _r = Store::load_from_asset(&ap, true, &mut report);
   4294 
   4295         println!("Error report for {}: {:?}", ap.display(), report.get_log());
   4296         assert!(!report.get_log().is_empty());
   4297         let errors = report_split_errors(report.get_log_mut());
   4298         assert!(errors[0].error_str().unwrap().starts_with("Prerelease"));
   4299     }
   4300 
   4301     #[test]
   4302     #[cfg(feature = "file_io")]
   4303     fn test_verifiable_credentials() {
   4304         use crate::utils::test::create_test_store;
   4305 
   4306         let signer = temp_signer();
   4307 
   4308         // test adding to actual image
   4309         let ap = fixture_path("earth_apollo17.jpg");
   4310         let temp_dir = tempdir().expect("temp dir");
   4311         let op = temp_dir_path(&temp_dir, "earth_apollo17.jpg");
   4312 
   4313         // get default store with default claim
   4314         let mut store = create_test_store().unwrap();
   4315 
   4316         // save to output
   4317         store
   4318             .save_to_asset(ap.as_path(), signer.as_ref(), op.as_path())
   4319             .unwrap();
   4320 
   4321         // read back in
   4322         let restored_store =
   4323             Store::load_from_asset(op.as_path(), true, &mut OneShotStatusTracker::new()).unwrap();
   4324 
   4325         let pc = restored_store.provenance_claim().unwrap();
   4326 
   4327         let vc = pc.get_verifiable_credentials();
   4328 
   4329         assert!(!vc.is_empty());
   4330         match &vc[0] {
   4331             AssertionData::Json(s) => {
   4332                 assert!(s.contains("did:nppa:eb1bb9934d9896a374c384521410c7f14"))
   4333             }
   4334             _ => panic!("expected JSON assertion data"),
   4335         }
   4336     }
   4337 
   4338     #[test]
   4339     #[cfg(feature = "file_io")]
   4340     fn test_data_box_creation() {
   4341         use crate::utils::test::create_test_store;
   4342 
   4343         let signer = temp_signer();
   4344 
   4345         // test adding to actual image
   4346         let ap = fixture_path("earth_apollo17.jpg");
   4347         let temp_dir = tempdir().expect("temp dir");
   4348         let op = temp_dir_path(&temp_dir, "earth_apollo17.jpg");
   4349 
   4350         // get default store with default claim
   4351         let mut store = create_test_store().unwrap();
   4352 
   4353         // save to output
   4354         store
   4355             .save_to_asset(ap.as_path(), signer.as_ref(), op.as_path())
   4356             .unwrap();
   4357 
   4358         // read back in
   4359         let restored_store =
   4360             Store::load_from_asset(op.as_path(), true, &mut OneShotStatusTracker::new()).unwrap();
   4361 
   4362         let pc = restored_store.provenance_claim().unwrap();
   4363 
   4364         let databoxes = pc.databoxes();
   4365 
   4366         assert!(!databoxes.is_empty());
   4367 
   4368         for (uri, db) in databoxes {
   4369             println!(
   4370                 "URI: {}, data: {}",
   4371                 uri.url(),
   4372                 String::from_utf8_lossy(&db.data)
   4373             );
   4374         }
   4375     }
   4376 
   4377     /// copies a fixture, replaces some bytes and returns a validation report
   4378     fn patch_and_report(
   4379         fixture_name: &str,
   4380         search_bytes: &[u8],
   4381         replace_bytes: &[u8],
   4382     ) -> impl StatusTracker {
   4383         let temp_dir = tempdir().expect("temp dir");
   4384         let path = temp_fixture_path(&temp_dir, fixture_name);
   4385         patch_file(&path, search_bytes, replace_bytes).expect("patch_file");
   4386         let mut report = DetailedStatusTracker::default();
   4387         let _r = Store::load_from_asset(&path, true, &mut report); // errs are in report
   4388         println!("report: {report:?}");
   4389         report
   4390     }
   4391 
   4392     #[test]
   4393     #[cfg(feature = "file_io")]
   4394     fn test_update_manifest() {
   4395         use crate::{hashed_uri::HashedUri, utils::test::create_test_store};
   4396 
   4397         let signer = temp_signer();
   4398 
   4399         // test adding to actual image
   4400         let ap = fixture_path("earth_apollo17.jpg");
   4401         let temp_dir = tempdir().expect("temp dir");
   4402         let op = temp_dir_path(&temp_dir, "update_manifest.jpg");
   4403 
   4404         // get default store with default claim
   4405         let mut store = create_test_store().unwrap();
   4406 
   4407         // save to output
   4408         store
   4409             .save_to_asset(ap.as_path(), signer.as_ref(), op.as_path())
   4410             .unwrap();
   4411 
   4412         let mut report = OneShotStatusTracker::default();
   4413         // read back in
   4414         let mut restored_store = Store::load_from_asset(op.as_path(), true, &mut report).unwrap();
   4415 
   4416         let pc = restored_store.provenance_claim().unwrap();
   4417 
   4418         // should be a regular manifest
   4419         assert!(!pc.update_manifest());
   4420 
   4421         // create a new update manifest
   4422         let mut claim = Claim::new("adobe unit test", Some("update_manfifest"));
   4423 
   4424         // must contain an ingredient
   4425         let parent_hashed_uri = HashedUri::new(
   4426             restored_store.provenance_path().unwrap(),
   4427             Some(pc.alg().to_string()),
   4428             &pc.hash(),
   4429         );
   4430 
   4431         let ingredient = Ingredient::new(
   4432             "update_manifest.jpg",
   4433             "image/jpeg",
   4434             "xmp.iid:7b57930e-2f23-47fc-affe-0400d70b738d",
   4435             Some("xmp.did:87d51599-286e-43b2-9478-88c79f49c347"),
   4436         )
   4437         .set_parent()
   4438         .set_c2pa_manifest_from_hashed_uri(Some(parent_hashed_uri));
   4439 
   4440         claim.add_assertion(&ingredient).unwrap();
   4441 
   4442         restored_store.commit_update_manifest(claim).unwrap();
   4443         restored_store
   4444             .save_to_asset(op.as_path(), signer.as_ref(), op.as_path())
   4445             .unwrap();
   4446 
   4447         // read back in store with update manifest
   4448         let um_store = Store::load_from_asset(op.as_path(), true, &mut report).unwrap();
   4449 
   4450         let um = um_store.provenance_claim().unwrap();
   4451 
   4452         // should be an update manifest
   4453         assert!(um.update_manifest());
   4454     }
   4455 
   4456     #[test]
   4457     fn test_claim_decoding() {
   4458         // modify a required field label in the claim - causes failure to read claim from cbor
   4459         let report = patch_and_report("C.jpg", b"claim_generator", b"claim_generatur");
   4460         assert!(!report.get_log().is_empty());
   4461         assert!(report.get_log()[0]
   4462             .error_str()
   4463             .unwrap()
   4464             .starts_with("ClaimDecoding"))
   4465     }
   4466 
   4467     #[test]
   4468     fn test_claim_modified() {
   4469         // replace the title that is inside the claim data - should cause signature to not match
   4470         let mut report = patch_and_report("C.jpg", b"C.jpg", b"X.jpg");
   4471         assert!(!report.get_log().is_empty());
   4472         let errors = report_split_errors(report.get_log_mut());
   4473 
   4474         assert!(report_has_err(&errors, Error::CoseSignature));
   4475         assert!(report_has_err(&errors, Error::CoseTimeStampMismatch));
   4476 
   4477         assert!(report_has_status(
   4478             &errors,
   4479             validation_status::CLAIM_SIGNATURE_MISMATCH
   4480         ));
   4481         assert!(report_has_status(
   4482             &errors,
   4483             validation_status::TIMESTAMP_MISMATCH
   4484         ));
   4485     }
   4486 
   4487     #[test]
   4488     fn test_assertion_hash_mismatch() {
   4489         // modifies content of an action assertion - causes an assertion hashuri mismatch
   4490         let mut report = patch_and_report("CA.jpg", b"brightnesscontrast", b"brightnesscontraxx");
   4491         let errors = report_split_errors(report.get_log_mut());
   4492 
   4493         assert_eq!(
   4494             errors[0].validation_status.as_deref(),
   4495             Some(validation_status::ASSERTION_HASHEDURI_MISMATCH)
   4496         );
   4497     }
   4498 
   4499     #[test]
   4500     fn test_claim_missing() {
   4501         // patch jumbf url from c2pa_manifest field in an ingredient to cause claim_missing
   4502         // note this includes hex for Jumbf blocks, so may need some manual tweaking
   4503         const SEARCH_BYTES: &[u8] =
   4504             b"c2pa_manifest\xA3\x63url\x78\x4aself#jumbf=/c2pa/contentauth:urn:uuid:";
   4505         const REPLACE_BYTES: &[u8] =
   4506             b"c2pa_manifest\xA3\x63url\x78\x4aself#jumbf=/c2pa/contentauth:urn:uuix:";
   4507         let mut report = patch_and_report("CIE-sig-CA.jpg", SEARCH_BYTES, REPLACE_BYTES);
   4508         let errors = report_split_errors(report.get_log_mut());
   4509         assert_eq!(
   4510             errors[0].validation_status.as_deref(),
   4511             Some(validation_status::ASSERTION_HASHEDURI_MISMATCH)
   4512         );
   4513         assert_eq!(
   4514             errors[1].validation_status.as_deref(),
   4515             Some(validation_status::CLAIM_MISSING)
   4516         );
   4517     }
   4518 
   4519     #[test]
   4520     fn test_display() {
   4521         let ap = fixture_path("CA.jpg");
   4522         let mut report = DetailedStatusTracker::new();
   4523         let store = Store::load_from_asset(&ap, true, &mut report).expect("load_from_asset");
   4524         let _errors = report_split_errors(report.get_log_mut());
   4525 
   4526         println!("store = {store}");
   4527     }
   4528 
   4529     #[test]
   4530     fn test_legacy_ingredient_hash() {
   4531         // test 1.0 ingredient hash
   4532         let ap = fixture_path("legacy_ingredient_hash.jpg");
   4533         let mut report = DetailedStatusTracker::new();
   4534         let store = Store::load_from_asset(&ap, true, &mut report).expect("load_from_asset");
   4535         println!("store = {store}");
   4536     }
   4537 
   4538     #[test]
   4539     fn test_bmff_legacy() {
   4540         // test 1.0 bmff hash
   4541         let ap = fixture_path("legacy.mp4");
   4542         let mut report = DetailedStatusTracker::new();
   4543         let store = Store::load_from_asset(&ap, true, &mut report).expect("load_from_asset");
   4544         println!("store = {store}");
   4545     }
   4546 
   4547     /*
   4548            #[test]
   4549            fn test_bmff_fragments() {
   4550                let init_stream_path = fixture_path("dashinit.mp4");
   4551                let segment_stream_path = fixture_path("dash1.m4s");
   4552 
   4553                let init_stream = std::fs::read(init_stream_path).unwrap();
   4554                let segment_stream = std::fs::read(segment_stream_path).unwrap();
   4555 
   4556                let mut report = DetailedStatusTracker::new();
   4557                let store = Store::load_fragment_from_memory(
   4558                    "mp4",
   4559                    &init_stream,
   4560                    &segment_stream,
   4561                    true,
   4562                    &mut report,
   4563                )
   4564                .expect("load_from_asset");
   4565                println!("store = {store}");
   4566            }
   4567     */
   4568 
   4569     #[test]
   4570     fn test_bmff_jumbf_generation() {
   4571         // test adding to actual image
   4572         let ap = fixture_path("video1.mp4");
   4573         let temp_dir = tempdir().expect("temp dir");
   4574         let op = temp_dir_path(&temp_dir, "video1.mp4");
   4575 
   4576         // Create claims store.
   4577         let mut store = Store::new();
   4578 
   4579         // Create a new claim.
   4580         let claim1 = create_test_claim().unwrap();
   4581 
   4582         let signer = temp_signer();
   4583 
   4584         // Move the claim to claims list.
   4585         store.commit_claim(claim1).unwrap();
   4586         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   4587 
   4588         let mut report = DetailedStatusTracker::new();
   4589 
   4590         // can we read back in
   4591         let _new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   4592     }
   4593 
   4594     #[test]
   4595     #[cfg(feature = "file_io")]
   4596     fn test_removed_jumbf() {
   4597         // test adding to actual image
   4598         let ap = fixture_path("no_manifest.jpg");
   4599 
   4600         let mut report = DetailedStatusTracker::new();
   4601 
   4602         // can we read back in
   4603         let _store = Store::load_from_asset(&ap, true, &mut report);
   4604 
   4605         assert!(report_has_err(report.get_log(), Error::JumbfNotFound));
   4606     }
   4607 
   4608     #[test]
   4609     fn test_external_manifest_sidecar() {
   4610         // test adding to actual image
   4611         let ap = fixture_path("libpng-test.png");
   4612         let temp_dir = tempdir().expect("temp dir");
   4613         let op = temp_dir_path(&temp_dir, "libpng-test-c2pa.png");
   4614 
   4615         let sidecar = op.with_extension(MANIFEST_STORE_EXT);
   4616 
   4617         // Create claims store.
   4618         let mut store = Store::new();
   4619 
   4620         // Create a new claim.
   4621         let mut claim = create_test_claim().unwrap();
   4622 
   4623         // set claim for side car generation
   4624         claim.set_external_manifest();
   4625 
   4626         // Do we generate JUMBF?
   4627         let signer = temp_signer();
   4628 
   4629         store.commit_claim(claim).unwrap();
   4630 
   4631         let saved_manifest = store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   4632 
   4633         assert!(sidecar.exists());
   4634 
   4635         // load external manifest
   4636         let loaded_manifest = std::fs::read(sidecar).unwrap();
   4637 
   4638         // compare returned to external
   4639         //assert_eq!(saved_manifest, loaded_manifest);
   4640 
   4641         // test auto loading of sidecar with validation
   4642         let mut validation_log = OneShotStatusTracker::default();
   4643         Store::load_from_asset(&op, true, &mut validation_log).unwrap();
   4644     }
   4645 
   4646     // generalize test for multipe file types
   4647     fn external_manifest_test(file_name: &str) {
   4648         // test adding to actual image
   4649         let ap = fixture_path(file_name);
   4650         let extension = ap.extension().unwrap().to_str().unwrap();
   4651         let temp_dir = tempdir().expect("temp dir");
   4652         let mut op = temp_dir_path(&temp_dir, file_name);
   4653         op.set_extension(extension);
   4654 
   4655         let sidecar = op.with_extension(MANIFEST_STORE_EXT);
   4656 
   4657         // Create claims store.
   4658         let mut store = Store::new();
   4659 
   4660         // Create a new claim.
   4661         let mut claim = create_test_claim().unwrap();
   4662 
   4663         // Do we generate JUMBF?
   4664         let signer = temp_signer();
   4665 
   4666         // start with base url
   4667         let fp = format!("file:/{}", sidecar.to_str().unwrap());
   4668         let url = url::Url::parse(&fp).unwrap();
   4669 
   4670         let url_string: String = url.into();
   4671 
   4672         // set claim for side car with remote manifest embedding generation
   4673         claim.set_remote_manifest(url_string.clone()).unwrap();
   4674 
   4675         store.commit_claim(claim).unwrap();
   4676 
   4677         let saved_manifest = store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   4678 
   4679         assert!(sidecar.exists());
   4680 
   4681         // load external manifest
   4682         let loaded_manifest = std::fs::read(sidecar).unwrap();
   4683 
   4684         // compare returned to external
   4685         //assert_eq!(saved_manifest, loaded_manifest);
   4686 
   4687         // load the jumbf back into a store
   4688         let mut asset_reader = std::fs::File::open(op.clone()).unwrap();
   4689         let ext_ref =
   4690             crate::utils::xmp_inmemory_utils::XmpInfo::from_source(&mut asset_reader, extension)
   4691                 .provenance
   4692                 .unwrap();
   4693 
   4694         assert_eq!(ext_ref, url_string);
   4695 
   4696         // make sure it validates
   4697         let mut validation_log = OneShotStatusTracker::default();
   4698         Store::load_from_asset(&op, true, &mut validation_log).unwrap();
   4699     }
   4700 
   4701     #[test]
   4702     fn test_external_manifest_embedded_png() {
   4703         external_manifest_test("libpng-test.png");
   4704     }
   4705 
   4706     #[test]
   4707     fn test_external_manifest_embedded_tiff() {
   4708         external_manifest_test("TUSCANY.TIF");
   4709     }
   4710 
   4711     #[test]
   4712     fn test_external_manifest_embedded_webp() {
   4713         external_manifest_test("sample1.webp");
   4714     }
   4715 
   4716     #[test]
   4717     fn test_user_guid_external_manifest_embedded() {
   4718         // test adding to actual image
   4719         let ap = fixture_path("libpng-test.png");
   4720         let temp_dir = tempdir().expect("temp dir");
   4721         let op = temp_dir_path(&temp_dir, "libpng-test-c2pa.png");
   4722 
   4723         let sidecar = op.with_extension(MANIFEST_STORE_EXT);
   4724 
   4725         // Create claims store.
   4726         let mut store = Store::new();
   4727 
   4728         // Create a new claim.
   4729         let mut claim = create_test_claim().unwrap();
   4730 
   4731         // Do we generate JUMBF?
   4732         let signer = temp_signer();
   4733 
   4734         // start with base url
   4735         let fp = format!("file:/{}", sidecar.to_str().unwrap());
   4736         let url = url::Url::parse(&fp).unwrap();
   4737 
   4738         let url_string: String = url.into();
   4739 
   4740         // set claim for side car with remote manifest embedding generation
   4741         claim.set_embed_remote_manifest(url_string.clone()).unwrap();
   4742 
   4743         store.commit_claim(claim).unwrap();
   4744 
   4745         let saved_manifest = store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   4746 
   4747         assert!(sidecar.exists());
   4748 
   4749         // load external manifest
   4750         let loaded_manifest = std::fs::read(sidecar).unwrap();
   4751 
   4752         // compare returned to external
   4753         //assert_eq!(saved_manifest, loaded_manifest);
   4754 
   4755         let mut asset_reader = std::fs::File::open(op.clone()).unwrap();
   4756         let ext_ref =
   4757             crate::utils::xmp_inmemory_utils::XmpInfo::from_source(&mut asset_reader, "png")
   4758                 .provenance
   4759                 .unwrap();
   4760 
   4761         assert_eq!(ext_ref, url_string);
   4762 
   4763         // make sure it validates
   4764         let mut validation_log = OneShotStatusTracker::default();
   4765         Store::load_from_asset(&op, true, &mut validation_log).unwrap();
   4766     }
   4767 
   4768     #[test]
   4769     fn test_external_manifest_from_memory() {
   4770         // test adding to actual image
   4771         let ap = fixture_path("libpng-test.png");
   4772         let temp_dir = tempdir().expect("temp dir");
   4773         let op = temp_dir_path(&temp_dir, "libpng-test-c2pa.png");
   4774 
   4775         let sidecar = op.with_extension(MANIFEST_STORE_EXT);
   4776 
   4777         // Create claims store.
   4778         let mut store = Store::new();
   4779 
   4780         // Create a new claim.
   4781         let mut claim = create_test_claim().unwrap();
   4782 
   4783         // Do we generate JUMBF?
   4784         let signer = temp_signer();
   4785 
   4786         // start with base url
   4787         let fp = format!("file:/{}", sidecar.to_str().unwrap());
   4788         let url = url::Url::parse(&fp).unwrap();
   4789 
   4790         let url_string: String = url.into();
   4791 
   4792         // set claim for side car with remote manifest embedding generation
   4793         claim.set_remote_manifest(url_string).unwrap();
   4794 
   4795         store.commit_claim(claim).unwrap();
   4796 
   4797         let saved_manifest = store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   4798 
   4799         // delete the sidecar so we can test for url only rea
   4800         // std::fs::remove_file(sidecar);
   4801 
   4802         assert!(sidecar.exists());
   4803 
   4804         // load external manifest
   4805         let loaded_manifest = std::fs::read(sidecar).unwrap();
   4806 
   4807         // compare returned to external
   4808         // assert_eq!(saved_manifest, loaded_manifest);
   4809 
   4810         // Load the exported file into a buffer
   4811         let file_buffer = std::fs::read(&op).unwrap();
   4812 
   4813         let mut validation_log = OneShotStatusTracker::default();
   4814         let result = Store::load_from_memory("png", &file_buffer, true, &mut validation_log);
   4815 
   4816         assert!(result.is_err());
   4817 
   4818         // We should get a `JumbfNotFound` error since the external reference points to a file URL, not a remote URL
   4819         match result {
   4820             Ok(_store) => panic!("did not expect to have a store"),
   4821             Err(e) => match e {
   4822                 Error::JumbfNotFound => {}
   4823                 e => panic!("unexpected error: {e}"),
   4824             },
   4825         }
   4826     }
   4827 
   4828     #[actix::test]
   4829     async fn test_jumbf_generation_stream() {
   4830         let file_buffer = include_bytes!("../tests/fixtures/earth_apollo17.jpg").to_vec();
   4831         // convert buffer to cursor with Read/Write/Seek capability
   4832         let mut buf_io = Cursor::new(file_buffer);
   4833 
   4834         // Create claims store.
   4835         let mut store = Store::new();
   4836 
   4837         // Create a new claim.
   4838         let claim1 = create_test_claim().unwrap();
   4839 
   4840         let signer = temp_signer();
   4841 
   4842         store.commit_claim(claim1).unwrap();
   4843 
   4844         let mut result: Vec<u8> = Vec::new();
   4845         let mut result_stream = Cursor::new(result);
   4846 
   4847         store
   4848             .save_to_stream("jpeg", &mut buf_io, &mut result_stream, signer.as_ref())
   4849             .unwrap();
   4850 
   4851         // convert our cursor back into a buffer
   4852         result = result_stream.into_inner();
   4853 
   4854         // make sure we can read from new file
   4855         let mut report = DetailedStatusTracker::new();
   4856         let _new_store = Store::load_from_memory("jpeg", &result, true, &mut report).unwrap();
   4857 
   4858         let errors = report_split_errors(report.get_log_mut());
   4859         assert!(errors.is_empty());
   4860 
   4861         // std::fs::write("target/test.jpg", result).unwrap();
   4862     }
   4863 
   4864     #[test]
   4865     #[cfg(feature = "file_io")]
   4866     fn test_tiff_jumbf_generation() {
   4867         // test adding to actual image
   4868         let ap = fixture_path("TUSCANY.TIF");
   4869         let temp_dir = tempdir().expect("temp dir");
   4870         let op = temp_dir_path(&temp_dir, "TUSCANY-OUTPUT.TIF");
   4871 
   4872         // Create claims store.
   4873         let mut store = Store::new();
   4874 
   4875         // Create a new claim.
   4876         let claim1 = create_test_claim().unwrap();
   4877 
   4878         // Create a new claim.
   4879         let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
   4880         create_editing_claim(&mut claim2).unwrap();
   4881 
   4882         // Create a 3rd party claim
   4883         let mut claim_capture = Claim::new("capture", Some("claim_capture"));
   4884         create_capture_claim(&mut claim_capture).unwrap();
   4885 
   4886         // Do we generate JUMBF?
   4887         let signer = temp_signer();
   4888 
   4889         // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
   4890         store.commit_claim(claim1).unwrap();
   4891         store.save_to_asset(&ap, signer.as_ref(), &op).unwrap();
   4892         store.commit_claim(claim_capture).unwrap();
   4893         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   4894         store.commit_claim(claim2).unwrap();
   4895         store.save_to_asset(&op, signer.as_ref(), &op).unwrap();
   4896 
   4897         println!("Provenance: {}\n", store.provenance_path().unwrap());
   4898 
   4899         let mut report = DetailedStatusTracker::new();
   4900 
   4901         // read from new file
   4902         let new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
   4903 
   4904         let errors = report_split_errors(report.get_log_mut());
   4905         assert!(errors.is_empty());
   4906 
   4907         // dump store and compare to original
   4908         for claim in new_store.claims() {
   4909             let _restored_json = claim
   4910                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   4911                 .unwrap();
   4912             let _orig_json = store
   4913                 .get_claim(claim.label())
   4914                 .unwrap()
   4915                 .to_json(AssertionStoreJsonFormat::OrderedList, false)
   4916                 .unwrap();
   4917 
   4918             println!(
   4919                 "Claim: {} \n{}",
   4920                 claim.label(),
   4921                 claim
   4922                     .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true)
   4923                     .expect("could not restore from json")
   4924             );
   4925 
   4926             for hashed_uri in claim.assertions() {
   4927                 let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url());
   4928                 claim
   4929                     .get_claim_assertion(&label, instance)
   4930                     .expect("Should find assertion");
   4931             }
   4932         }
   4933     }
   4934 
   4935     #[actix::test]
   4936     #[cfg(feature = "file_io")]
   4937     async fn test_boxhash_embeddable_manifest_async() {
   4938         // test adding to actual image
   4939         let ap = fixture_path("boxhash.jpg");
   4940         let box_hash_path = fixture_path("boxhash.json");
   4941 
   4942         // Create claims store.
   4943         let mut store = Store::new();
   4944 
   4945         // Create a new claim.
   4946         let mut claim = create_test_claim().unwrap();
   4947 
   4948         // add box hash for CA.jpg
   4949         let box_hash_data = std::fs::read(box_hash_path).unwrap();
   4950         let assertion = Assertion::from_data_json(BOX_HASH, &box_hash_data).unwrap();
   4951         let box_hash = BoxHash::from_json_assertion(&assertion).unwrap();
   4952         claim.add_assertion(&box_hash).unwrap();
   4953 
   4954         store.commit_claim(claim).unwrap();
   4955 
   4956         // Do we generate JUMBF?
   4957         let signer = crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256);
   4958 
   4959         // get the embeddable manifest
   4960         let em = store
   4961             .get_box_hashed_embeddable_manifest_async(&signer)
   4962             .await
   4963             .unwrap();
   4964 
   4965         // get composed version for embedding to JPEG
   4966         let cm = Store::get_composed_manifest(&em, "jpg").unwrap();
   4967 
   4968         // insert manifest into output asset
   4969         let jpeg_io = get_assetio_handler_from_path(&ap).unwrap();
   4970         let ol = jpeg_io.get_object_locations(&ap).unwrap();
   4971 
   4972         let cai_loc = ol
   4973             .iter()
   4974             .find(|o| o.htype == HashBlockObjectType::Cai)
   4975             .unwrap();
   4976 
   4977         // remove any existing manifest
   4978         jpeg_io.read_cai_store(&ap).unwrap();
   4979 
   4980         // build new asset in memory inserting new manifest
   4981         let outbuf = Vec::new();
   4982         let mut out_stream = Cursor::new(outbuf);
   4983         let mut input_file = std::fs::File::open(&ap).unwrap();
   4984 
   4985         // write before
   4986         let mut before = vec![0u8; cai_loc.offset];
   4987         input_file.read_exact(before.as_mut_slice()).unwrap();
   4988         out_stream.write_all(&before).unwrap();
   4989 
   4990         // write composed bytes
   4991         out_stream.write_all(&cm).unwrap();
   4992 
   4993         // write bytes after
   4994         let mut after_buf = Vec::new();
   4995         input_file.read_to_end(&mut after_buf).unwrap();
   4996         out_stream.write_all(&after_buf).unwrap();
   4997 
   4998         // save to output file
   4999         let temp_dir = tempfile::tempdir().unwrap();
   5000         let output = temp_dir_path(&temp_dir, "boxhash-out.jpg");
   5001         let mut output_file = std::fs::OpenOptions::new()
   5002             .read(true)
   5003             .write(true)
   5004             .create(true)
   5005             .truncate(true)
   5006             .open(&output)
   5007             .unwrap();
   5008         output_file.write_all(&out_stream.into_inner()).unwrap();
   5009 
   5010         let mut report = DetailedStatusTracker::new();
   5011         let _new_store = Store::load_from_asset(&output, true, &mut report).unwrap();
   5012 
   5013         let errors = report_split_errors(report.get_log_mut());
   5014         assert!(errors.is_empty());
   5015     }
   5016 
   5017     #[test]
   5018     #[cfg(feature = "file_io")]
   5019     fn test_boxhash_embeddable_manifest() {
   5020         // test adding to actual image
   5021         let ap = fixture_path("boxhash.jpg");
   5022         let box_hash_path = fixture_path("boxhash.json");
   5023 
   5024         // Create claims store.
   5025         let mut store = Store::new();
   5026 
   5027         // Create a new claim.
   5028         let mut claim = create_test_claim().unwrap();
   5029 
   5030         // add box hash for CA.jpg
   5031         let box_hash_data = std::fs::read(box_hash_path).unwrap();
   5032         let assertion = Assertion::from_data_json(BOX_HASH, &box_hash_data).unwrap();
   5033         let box_hash = BoxHash::from_json_assertion(&assertion).unwrap();
   5034         claim.add_assertion(&box_hash).unwrap();
   5035 
   5036         store.commit_claim(claim).unwrap();
   5037 
   5038         // Do we generate JUMBF?
   5039         let signer = temp_signer();
   5040 
   5041         // get the embeddable manifest
   5042         let em = store
   5043             .get_box_hashed_embeddable_manifest(signer.as_ref())
   5044             .unwrap();
   5045 
   5046         // get composed version for embedding to JPEG
   5047         let cm = Store::get_composed_manifest(&em, "jpg").unwrap();
   5048 
   5049         // insert manifest into output asset
   5050         let jpeg_io = get_assetio_handler_from_path(&ap).unwrap();
   5051         let ol = jpeg_io.get_object_locations(&ap).unwrap();
   5052 
   5053         let cai_loc = ol
   5054             .iter()
   5055             .find(|o| o.htype == HashBlockObjectType::Cai)
   5056             .unwrap();
   5057 
   5058         // remove any existing manifest
   5059         jpeg_io.read_cai_store(&ap).unwrap();
   5060 
   5061         // build new asset in memory inserting new manifest
   5062         let outbuf = Vec::new();
   5063         let mut out_stream = Cursor::new(outbuf);
   5064         let mut input_file = std::fs::File::open(&ap).unwrap();
   5065 
   5066         // write before
   5067         let mut before = vec![0u8; cai_loc.offset];
   5068         input_file.read_exact(before.as_mut_slice()).unwrap();
   5069         out_stream.write_all(&before).unwrap();
   5070 
   5071         // write composed bytes
   5072         out_stream.write_all(&cm).unwrap();
   5073 
   5074         // write bytes after
   5075         let mut after_buf = Vec::new();
   5076         input_file.read_to_end(&mut after_buf).unwrap();
   5077         out_stream.write_all(&after_buf).unwrap();
   5078 
   5079         // save to output file
   5080         let temp_dir = tempfile::tempdir().unwrap();
   5081         let output = temp_dir_path(&temp_dir, "boxhash-out.jpg");
   5082         let mut output_file = std::fs::OpenOptions::new()
   5083             .read(true)
   5084             .write(true)
   5085             .create(true)
   5086             .truncate(true)
   5087             .open(&output)
   5088             .unwrap();
   5089         output_file.write_all(&out_stream.into_inner()).unwrap();
   5090 
   5091         let mut report = DetailedStatusTracker::new();
   5092         let _new_store = Store::load_from_asset(&output, true, &mut report).unwrap();
   5093 
   5094         let errors = report_split_errors(report.get_log_mut());
   5095         assert!(errors.is_empty());
   5096     }
   5097 
   5098     #[actix::test]
   5099     #[cfg(feature = "file_io")]
   5100     async fn test_datahash_embeddable_manifest_async() {
   5101         // test adding to actual image
   5102         let ap = fixture_path("cloud.jpg");
   5103 
   5104         // Do we generate JUMBF?
   5105         let signer = crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256);
   5106 
   5107         // Create claims store.
   5108         let mut store = Store::new();
   5109 
   5110         // Create a new claim.
   5111         let claim = create_test_claim().unwrap();
   5112 
   5113         store.commit_claim(claim).unwrap();
   5114 
   5115         // get a placeholder the manifest
   5116         let placeholder = store
   5117             .get_data_hashed_manifest_placeholder(signer.reserve_size(), "jpeg")
   5118             .unwrap();
   5119 
   5120         let temp_dir = tempfile::tempdir().unwrap();
   5121         let output = temp_dir_path(&temp_dir, "boxhash-out.jpg");
   5122         let mut output_file = std::fs::OpenOptions::new()
   5123             .read(true)
   5124             .write(true)
   5125             .create(true)
   5126             .truncate(true)
   5127             .open(&output)
   5128             .unwrap();
   5129 
   5130         // write a jpeg file with a placeholder for the manifest (returns offset of the placeholder)
   5131         let offset =
   5132             write_jpeg_placeholder_file(&placeholder, &ap, &mut output_file, None).unwrap();
   5133 
   5134         // build manifest to insert in the hole
   5135 
   5136         // create an hash exclusion for the manifest
   5137         let exclusion = HashRange::new(offset, placeholder.len());
   5138         let exclusions = vec![exclusion];
   5139 
   5140         let mut dh = DataHash::new("source_hash", "sha256");
   5141         dh.exclusions = Some(exclusions);
   5142 
   5143         // get the embeddable manifest, letting API do the hashing
   5144         output_file.rewind().unwrap();
   5145         let cm = store
   5146             .get_data_hashed_embeddable_manifest_async(&dh, &signer, "jpeg", Some(&mut output_file))
   5147             .await
   5148             .unwrap();
   5149 
   5150         // path in new composed manifest
   5151         output_file.seek(SeekFrom::Start(offset as u64)).unwrap();
   5152         output_file.write_all(&cm).unwrap();
   5153 
   5154         let mut report = DetailedStatusTracker::new();
   5155         let _new_store = Store::load_from_asset(&output, true, &mut report).unwrap();
   5156 
   5157         let errors = report_split_errors(report.get_log_mut());
   5158         assert!(errors.is_empty());
   5159     }
   5160 
   5161     #[test]
   5162     #[cfg(feature = "file_io")]
   5163     fn test_datahash_embeddable_manifest() {
   5164         // test adding to actual image
   5165         let ap = fixture_path("cloud.jpg");
   5166 
   5167         // Do we generate JUMBF?
   5168         let signer = temp_signer();
   5169 
   5170         // Create claims store.
   5171         let mut store = Store::new();
   5172 
   5173         // Create a new claim.
   5174         let claim = create_test_claim().unwrap();
   5175 
   5176         store.commit_claim(claim).unwrap();
   5177 
   5178         // get a placeholder the manifest
   5179         let placeholder = store
   5180             .get_data_hashed_manifest_placeholder(signer.reserve_size(), "jpeg")
   5181             .unwrap();
   5182 
   5183         let temp_dir = tempfile::tempdir().unwrap();
   5184         let output = temp_dir_path(&temp_dir, "boxhash-out.jpg");
   5185         let mut output_file = std::fs::OpenOptions::new()
   5186             .read(true)
   5187             .write(true)
   5188             .create(true)
   5189             .truncate(true)
   5190             .open(&output)
   5191             .unwrap();
   5192 
   5193         // write a jpeg file with a placeholder for the manifest (returns offset of the placeholder)
   5194         let offset =
   5195             write_jpeg_placeholder_file(&placeholder, &ap, &mut output_file, None).unwrap();
   5196 
   5197         // build manifest to insert in the hole
   5198 
   5199         // create an hash exclusion for the manifest
   5200         let exclusion = HashRange::new(offset, placeholder.len());
   5201         let exclusions = vec![exclusion];
   5202 
   5203         let mut dh = DataHash::new("source_hash", "sha256");
   5204         dh.exclusions = Some(exclusions);
   5205 
   5206         // get the embeddable manifest, letting API do the hashing
   5207         output_file.rewind().unwrap();
   5208         let cm = store
   5209             .get_data_hashed_embeddable_manifest(
   5210                 &dh,
   5211                 signer.as_ref(),
   5212                 "jpeg",
   5213                 Some(&mut output_file),
   5214             )
   5215             .unwrap();
   5216 
   5217         // path in new composed manifest
   5218         output_file.seek(SeekFrom::Start(offset as u64)).unwrap();
   5219         output_file.write_all(&cm).unwrap();
   5220 
   5221         let mut report = DetailedStatusTracker::new();
   5222         let _new_store = Store::load_from_asset(&output, true, &mut report).unwrap();
   5223 
   5224         let errors = report_split_errors(report.get_log_mut());
   5225         assert!(errors.is_empty());
   5226     }
   5227 
   5228     #[test]
   5229     #[cfg(feature = "file_io")]
   5230     fn test_datahash_embeddable_manifest_user_hashed() {
   5231         // test adding to actual image
   5232         let ap = fixture_path("cloud.jpg");
   5233 
   5234         let mut hasher = Hasher::SHA256(Sha256::new());
   5235 
   5236         // Do we generate JUMBF?
   5237         let signer = temp_signer();
   5238 
   5239         // Create claims store.
   5240         let mut store = Store::new();
   5241 
   5242         // Create a new claim.
   5243         let claim = create_test_claim().unwrap();
   5244 
   5245         store.commit_claim(claim).unwrap();
   5246 
   5247         // get a placeholder for the manifest
   5248         let placeholder = store
   5249             .get_data_hashed_manifest_placeholder(signer.reserve_size(), "jpeg")
   5250             .unwrap();
   5251 
   5252         let temp_dir = tempfile::tempdir().unwrap();
   5253         let output = temp_dir_path(&temp_dir, "boxhash-out.jpg");
   5254         let mut output_file = std::fs::OpenOptions::new()
   5255             .read(true)
   5256             .write(true)
   5257             .create(true)
   5258             .truncate(true)
   5259             .open(&output)
   5260             .unwrap();
   5261 
   5262         // write a jpeg file with a placeholder for the manifest (returns offset of the placeholder)
   5263         let offset =
   5264             write_jpeg_placeholder_file(&placeholder, &ap, &mut output_file, Some(&mut hasher))
   5265                 .unwrap();
   5266 
   5267         // create target data hash
   5268         // create an hash exclusion for the manifest
   5269         let exclusion = HashRange::new(offset, placeholder.len());
   5270         let exclusions = vec![exclusion];
   5271 
   5272         //input_file.rewind().unwrap();
   5273         let mut dh = DataHash::new("source_hash", "sha256");
   5274         dh.hash = Hasher::finalize(hasher);
   5275         dh.exclusions = Some(exclusions);
   5276 
   5277         // get the embeddable manifest, using user hashing
   5278         let cm = store
   5279             .get_data_hashed_embeddable_manifest(&dh, signer.as_ref(), "jpeg", None)
   5280             .unwrap();
   5281 
   5282         // path in new composed manifest
   5283         output_file.seek(SeekFrom::Start(offset as u64)).unwrap();
   5284         output_file.write_all(&cm).unwrap();
   5285 
   5286         let mut report = DetailedStatusTracker::new();
   5287         let _new_store = Store::load_from_asset(&output, true, &mut report).unwrap();
   5288 
   5289         let errors = report_split_errors(report.get_log_mut());
   5290         assert!(errors.is_empty());
   5291     }
   5292 }