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

claim.rs (80289B)


      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 #[cfg(feature = "file_io")]
     15 use std::path::Path;
     16 use std::{collections::HashMap, fmt};
     17 
     18 use chrono::{DateTime, Utc};
     19 use serde::{Deserialize, Serialize};
     20 use serde_json::{json, Map, Value};
     21 use uuid::Uuid;
     22 
     23 use crate::{
     24     assertion::{
     25         get_thumbnail_image_type, get_thumbnail_instance, get_thumbnail_type, Assertion,
     26         AssertionBase, AssertionData,
     27     },
     28     assertions::{
     29         self,
     30         labels::{self, CLAIM},
     31         AssetType, BmffHash, BoxHash, DataBox, DataHash,
     32     },
     33     asset_io::CAIRead,
     34     cose_validator::{get_signing_info, verify_cose, verify_cose_async},
     35     error::{Error, Result},
     36     hashed_uri::HashedUri,
     37     jumbf::{
     38         self,
     39         boxes::{
     40             CAICBORAssertionBox, CAIJSONAssertionBox, CAIUUIDAssertionBox, JumbfEmbeddedFileBox,
     41         },
     42         labels::{
     43             box_name_from_uri, manifest_label_from_uri, to_databox_uri, ASSERTIONS, CREDENTIALS,
     44             DATABOX, DATABOXES, SIGNATURE,
     45         },
     46     },
     47     jumbf_io::get_assetio_handler,
     48     salt::{DefaultSalt, SaltGenerator, NO_SALT},
     49     status_tracker::{log_item, OneShotStatusTracker, StatusTracker},
     50     trust_handler::TrustHandlerConfig,
     51     utils::{
     52         base64,
     53         hash_utils::{hash_by_alg, vec_compare, verify_by_alg},
     54     },
     55     validation_status,
     56     validator::ValidationInfo,
     57     ClaimGeneratorInfo,
     58 };
     59 
     60 const BUILD_HASH_ALG: &str = "sha256";
     61 
     62 /// JSON structure representing an Assertion reference in a Claim's "assertions" list
     63 use HashedUri as C2PAAssertion;
     64 
     65 const GH_FULL_VERSION_LIST: &str = "Sec-CH-UA-Full-Version-List";
     66 const GH_UA: &str = "Sec-CH-UA";
     67 
     68 // Enum to encapsulate the data type of the source asset.  This simplifies
     69 // having different implementations for functions as a single entry point can be
     70 // used to handle different data types.
     71 pub enum ClaimAssetData<'a> {
     72     #[cfg(feature = "file_io")]
     73     Path(&'a Path),
     74     Bytes(&'a [u8], &'a str),
     75     Stream(&'a mut dyn CAIRead, &'a str),
     76     StreamFragment(&'a mut dyn CAIRead, &'a mut dyn CAIRead, &'a str),
     77 }
     78 
     79 // helper struct to allow arbitrary order for assertions stored in jumbf.  The instance is
     80 // stored separate from the Assertion to allow for late binding to the label.  Also,
     81 // we can load assertions in any order and know the position without re-parsing label. We also
     82 // save on parsing the cbor assertion each time we need its contents
     83 #[derive(PartialEq, Eq, Clone)]
     84 pub struct ClaimAssertion {
     85     assertion: Assertion,
     86     instance: usize,
     87     hash_val: Vec<u8>,
     88     hash_alg: String,
     89     salt: Option<Vec<u8>>,
     90 }
     91 
     92 impl ClaimAssertion {
     93     pub fn new(
     94         assertion: Assertion,
     95         instance: usize,
     96         hashval: &[u8],
     97         alg: &str,
     98         salt: Option<Vec<u8>>,
     99     ) -> ClaimAssertion {
    100         ClaimAssertion {
    101             assertion,
    102             instance,
    103             hash_val: hashval.to_vec(),
    104             hash_alg: alg.to_string(),
    105             salt,
    106         }
    107     }
    108 
    109     pub fn update_assertion(&mut self, assertion: Assertion, hash: Vec<u8>) -> Result<()> {
    110         self.hash_val = hash;
    111         self.assertion = assertion;
    112         Ok(())
    113     }
    114 
    115     pub fn label(&self) -> String {
    116         let al_ref = self.assertion.label();
    117         if self.instance > 0 {
    118             if get_thumbnail_type(&al_ref) == labels::INGREDIENT_THUMBNAIL {
    119                 format!(
    120                     "{}__{}.{}",
    121                     get_thumbnail_type(&al_ref),
    122                     self.instance,
    123                     get_thumbnail_image_type(&al_ref)
    124                 )
    125             } else {
    126                 format!("{}__{}", al_ref, self.instance)
    127             }
    128         } else {
    129             self.assertion.label()
    130         }
    131     }
    132 
    133     pub const fn instance(&self) -> usize {
    134         self.instance
    135     }
    136 
    137     pub fn instance_string(&self) -> String {
    138         format!("{}", self.instance)
    139     }
    140 
    141     pub fn label_raw(&self) -> String {
    142         self.assertion.label()
    143     }
    144 
    145     pub const fn assertion(&self) -> &Assertion {
    146         &self.assertion
    147     }
    148 
    149     pub fn hash(&self) -> &[u8] {
    150         &self.hash_val
    151     }
    152 
    153     pub const fn salt(&self) -> &Option<Vec<u8>> {
    154         &self.salt
    155     }
    156 
    157     pub fn hash_alg(&self) -> &str {
    158         &self.hash_alg
    159     }
    160 
    161     /// returns true if assertions are of the same enum variant
    162     pub fn is_same_type(&self, input_assertion: &Assertion) -> bool {
    163         Assertion::assertions_eq(&self.assertion, input_assertion)
    164     }
    165 }
    166 
    167 impl fmt::Debug for ClaimAssertion {
    168     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    169         write!(f, "{:?}, instance: {}", self.assertion, self.instance)
    170     }
    171 }
    172 
    173 /// A `Claim` gathers together all the `Assertion`s about an asset
    174 /// from an actor at a given time, and may also include one or more
    175 /// hashes of the asset itself, and a reference to the previous `Claim`.
    176 ///
    177 /// It has all the same properties as an `Assertion` including being
    178 /// assigned a label (`c2pa.claim.v1`) and being either embedded into the
    179 /// asset or in the cloud. The claim is cryptographically hashed and
    180 /// that hash is signed to produce the claim signature.
    181 #[derive(Deserialize, Serialize, Debug, Default, Clone)]
    182 pub struct Claim {
    183     // external manifest
    184     #[serde(skip_deserializing, skip_serializing)]
    185     remote_manifest: RemoteManifest,
    186 
    187     // root of CAI store
    188     #[serde(skip_deserializing, skip_serializing)]
    189     update_manifest: bool,
    190 
    191     #[serde(skip_serializing_if = "Option::is_none", rename = "dc:title")]
    192     pub title: Option<String>, // title for this claim, generally the name of the containing asset
    193 
    194     #[serde(rename = "dc:format")]
    195     pub format: String, // mime format of document containing this claim
    196 
    197     #[serde(rename = "instanceID")]
    198     pub instance_id: String, // instance Id of document containing this claim
    199 
    200     // Internal list of ingredients
    201     #[serde(skip_deserializing, skip_serializing)]
    202     ingredients_store: HashMap<String, Vec<Claim>>,
    203 
    204     // internal scratch objects
    205     #[serde(skip_deserializing, skip_serializing)]
    206     box_prefix: String, // where in JUMBF hierarchy should this claim exist
    207 
    208     #[serde(skip_deserializing, skip_serializing)]
    209     signature_val: Vec<u8>, // the signature of the loaded/saved claim
    210 
    211     // root of CAI store
    212     #[serde(skip_deserializing, skip_serializing)]
    213     #[allow(dead_code)]
    214     root: String,
    215 
    216     // internal scratch objects
    217     #[serde(skip_deserializing, skip_serializing)]
    218     label: String, // label of claim
    219 
    220     // Internal list of assertions for claim.
    221     // These are serialized manually based on need.
    222     #[serde(skip_deserializing, skip_serializing)]
    223     assertion_store: Vec<ClaimAssertion>,
    224 
    225     // Internal list of verifiable credentials for claim.
    226     // These are serialized manually based on need.
    227     #[serde(skip_deserializing, skip_serializing)]
    228     vc_store: Vec<(HashedUri, AssertionData)>,
    229 
    230     claim_generator: String, // generator of this claim
    231 
    232     pub(crate) claim_generator_info: Option<Vec<ClaimGeneratorInfo>>, /* detailed generator info of this claim */
    233 
    234     signature: String,              // link to signature box
    235     assertions: Vec<C2PAAssertion>, // list of assertion hashed URIs
    236 
    237     // original JSON bytes of claim; only present when reading from asset
    238     #[serde(skip_deserializing, skip_serializing)]
    239     original_bytes: Option<Vec<u8>>,
    240 
    241     // original JUMBF box order need to recalculate JUMBF box hash
    242     #[serde(skip_deserializing, skip_serializing)]
    243     original_box_order: Option<Vec<&'static str>>,
    244 
    245     #[serde(skip_serializing_if = "Option::is_none")]
    246     redacted_assertions: Option<Vec<String>>, // list of redacted assertions
    247 
    248     #[serde(skip_serializing_if = "Option::is_none")]
    249     alg: Option<String>, // hashing algorithm (default to Sha256)
    250 
    251     #[serde(skip_serializing_if = "Option::is_none")]
    252     alg_soft: Option<String>, // hashing algorithm for soft bindings
    253 
    254     #[serde(skip_serializing_if = "Option::is_none")]
    255     claim_generator_hints: Option<HashMap<String, Value>>,
    256 
    257     #[serde(skip_deserializing, skip_serializing)]
    258     data_boxes: Vec<(HashedUri, DataBox)>, /* list of the data boxes and their hashed URIs found for this manifest */
    259 }
    260 
    261 /// Enum to define how assertions are are stored when output to json
    262 pub enum AssertionStoreJsonFormat {
    263     None,                // no assertion store
    264     KeyValue,            // key (uri), value (Assertion json object)
    265     KeyValueNoBinary,    // KeyValue omitting binary results
    266     OrderedList,         // list of Assertions as json objects
    267     OrderedListNoBinary, // list of Assertions as json objects omitting binaries results
    268 }
    269 
    270 /// Remote manifest options. Use 'set_remote_manifest' to generate external manifests.
    271 #[derive(Clone, Debug, PartialEq, Eq)]
    272 pub enum RemoteManifest {
    273     NoRemote,                // No external manifest (default)
    274     SideCar,        // Manifest will be saved as a side car file, output asset is untouched.
    275     Remote(String), /* Manifest will be saved as a side car file, output asset will contain remote reference */
    276     EmbedWithRemote(String), /* Manifest will be embedded with a remote reference, sidecar will be generated */
    277 }
    278 
    279 impl Default for RemoteManifest {
    280     fn default() -> Self {
    281         Self::NoRemote
    282     }
    283 }
    284 
    285 #[derive(Serialize, Deserialize, Debug)]
    286 pub struct JsonOrderedAssertionData {
    287     label: String,
    288     data: Value,
    289     hash: String,
    290     is_binary: bool,
    291     mime_type: String,
    292 }
    293 
    294 impl Claim {
    295     /// Label prefix for a claim assertion.
    296     ///
    297     /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_overview_4>.
    298     pub const LABEL: &'static str = assertions::labels::CLAIM;
    299 
    300     /// Create a new claim.
    301     /// vendor: name used to label the claim (unique instance number is automatically calculated)
    302     /// claim_generator: User agent see c2pa spec for format
    303     pub fn new<S: Into<String>>(claim_generator: S, vendor: Option<&str>) -> Self {
    304         let urn = Uuid::new_v4();
    305         let l = match vendor {
    306             Some(v) => format!(
    307                 "{}:{}",
    308                 v.to_lowercase(),
    309                 urn.urn().encode_lower(&mut Uuid::encode_buffer())
    310             ),
    311             None => urn
    312                 .urn()
    313                 .encode_lower(&mut Uuid::encode_buffer())
    314                 .to_string(),
    315         };
    316 
    317         Claim {
    318             remote_manifest: RemoteManifest::NoRemote,
    319             box_prefix: "self#jumbf".to_string(),
    320             root: jumbf::labels::MANIFEST_STORE.to_string(),
    321             signature_val: Vec::new(),
    322             ingredients_store: HashMap::new(),
    323             label: l,
    324             signature: "".to_string(),
    325 
    326             claim_generator: claim_generator.into(),
    327             claim_generator_info: None,
    328             assertion_store: Vec::new(),
    329             vc_store: Vec::new(),
    330             assertions: Vec::new(),
    331             original_bytes: None,
    332             original_box_order: None,
    333             redacted_assertions: None,
    334             alg: Some(BUILD_HASH_ALG.to_string()),
    335             alg_soft: None,
    336             claim_generator_hints: None,
    337 
    338             title: None,
    339             format: "".to_string(),
    340             instance_id: "".to_string(),
    341 
    342             update_manifest: false,
    343             data_boxes: Vec::new(),
    344         }
    345     }
    346 
    347     /// Create a new claim with a user supplied GUID.
    348     /// user_guid: is user supplied guid conforming the C2PA spec for manifest names
    349     /// claim_generator: User agent see c2pa spec for format
    350     pub fn new_with_user_guid<S: Into<String>>(claim_generator: S, user_guid: S) -> Self {
    351         Claim {
    352             remote_manifest: RemoteManifest::NoRemote,
    353             box_prefix: "self#jumbf".to_string(),
    354             root: jumbf::labels::MANIFEST_STORE.to_string(),
    355             signature_val: Vec::new(),
    356             ingredients_store: HashMap::new(),
    357             label: user_guid.into(), // todo figure out how to validate this
    358             signature: "".to_string(),
    359 
    360             claim_generator: claim_generator.into(),
    361             claim_generator_info: None,
    362             assertion_store: Vec::new(),
    363             vc_store: Vec::new(),
    364             assertions: Vec::new(),
    365             original_bytes: None,
    366             original_box_order: None,
    367             redacted_assertions: None,
    368             alg: Some(BUILD_HASH_ALG.into()),
    369             alg_soft: None,
    370             claim_generator_hints: None,
    371 
    372             title: None,
    373             format: "".to_string(),
    374             instance_id: "".to_string(),
    375 
    376             update_manifest: false,
    377             data_boxes: Vec::new(),
    378         }
    379     }
    380 
    381     /// Build a claim and verify its integrity.
    382     pub fn build(&mut self) -> Result<()> {
    383         // A claim must have a signature box.
    384         if self.signature.is_empty() {
    385             self.add_signature_box_link();
    386         }
    387 
    388         Ok(())
    389     }
    390 
    391     /// return version this claim supports
    392     pub const fn build_version() -> &'static str {
    393         Self::LABEL
    394     }
    395 
    396     /// Return the JUMBF label for this claim.
    397     pub fn label(&self) -> &str {
    398         &self.label
    399     }
    400 
    401     /// Return the JUMBF URI for this claim.
    402     pub fn uri(&self) -> String {
    403         jumbf::labels::to_manifest_uri(&self.label)
    404     }
    405 
    406     /// Return the JUMBF URI for an assertion on this claim.
    407     pub fn assertion_uri(&self, assertion_label: &str) -> String {
    408         jumbf::labels::to_assertion_uri(&self.label, assertion_label)
    409     }
    410 
    411     /// Return the JUMBF Signature URI for this claim.
    412     pub fn signature_uri(&self) -> String {
    413         jumbf::labels::to_signature_uri(&self.label)
    414     }
    415 
    416     // Add link to the signature box for this claim.
    417     fn add_signature_box_link(&mut self) {
    418         self.signature = format!("{}={}", self.box_prefix, jumbf::labels::SIGNATURE);
    419     }
    420 
    421     ///  set signature of the claim
    422     pub(crate) fn set_signature_val(&mut self, signature: Vec<u8>) {
    423         self.signature_val = signature;
    424     }
    425 
    426     ///  get signature of the claim
    427     pub const fn signature_val(&self) -> &Vec<u8> {
    428         &self.signature_val
    429     }
    430 
    431     /// get claim generator
    432     pub fn claim_generator(&self) -> &str {
    433         &self.claim_generator
    434     }
    435 
    436     /// get format
    437     pub fn format(&self) -> &str {
    438         &self.format
    439     }
    440 
    441     /// get instance_id
    442     pub fn instance_id(&self) -> &str {
    443         &self.instance_id
    444     }
    445 
    446     /// set title
    447     pub fn set_title(&mut self, title: Option<String>) {
    448         self.title = title;
    449     }
    450 
    451     /// get title
    452     pub const fn title(&self) -> Option<&String> {
    453         self.title.as_ref()
    454     }
    455 
    456     /// order for which to generate the JUMBF boxes with writing manifest
    457     pub fn set_box_order(&mut self, box_order: Vec<&'static str>) {
    458         self.original_box_order = Some(box_order);
    459     }
    460 
    461     /// order to process
    462     pub fn get_box_order(&self) -> &[&str] {
    463         const DEFAULT_MANIFEST_ORDER: [&str; 5] =
    464             [ASSERTIONS, CLAIM, SIGNATURE, CREDENTIALS, DATABOXES];
    465 
    466         if let Some(bo) = &self.original_box_order {
    467             bo
    468         } else {
    469             &DEFAULT_MANIFEST_ORDER
    470         }
    471     }
    472 
    473     /// get algorithm
    474     pub fn alg(&self) -> &str {
    475         match self.alg.as_ref() {
    476             Some(alg) => alg,
    477             None => BUILD_HASH_ALG,
    478         }
    479     }
    480 
    481     /// get soft algorithm
    482     pub const fn alg_soft(&self) -> Option<&String> {
    483         self.alg_soft.as_ref()
    484     }
    485 
    486     /// Is this an update manifest
    487     pub const fn update_manifest(&self) -> bool {
    488         self.update_manifest
    489     }
    490 
    491     pub fn set_remote_manifest<S: Into<String> + AsRef<str>>(
    492         &mut self,
    493         remote_url: S,
    494     ) -> Result<()> {
    495         let url = url::Url::parse(remote_url.as_ref())
    496             .map_err(|_e| Error::BadParam("remote url is badly formed".to_string()))?;
    497         self.remote_manifest = RemoteManifest::Remote(url.to_string());
    498 
    499         Ok(())
    500     }
    501 
    502     pub fn set_embed_remote_manifest<S: Into<String> + AsRef<str>>(
    503         &mut self,
    504         remote_url: S,
    505     ) -> Result<()> {
    506         let url = url::Url::parse(remote_url.as_ref())
    507             .map_err(|_e| Error::BadParam("remote url is badly formed".to_string()))?;
    508         self.remote_manifest = RemoteManifest::EmbedWithRemote(url.to_string());
    509 
    510         Ok(())
    511     }
    512 
    513     pub fn set_external_manifest(&mut self) {
    514         self.remote_manifest = RemoteManifest::SideCar;
    515     }
    516 
    517     pub(crate) fn remote_manifest(&self) -> RemoteManifest {
    518         self.remote_manifest.clone()
    519     }
    520 
    521     pub(crate) fn set_update_manifest(&mut self, is_update_manifest: bool) {
    522         self.update_manifest = is_update_manifest;
    523     }
    524 
    525     pub fn add_claim_generator_info(&mut self, info: ClaimGeneratorInfo) -> &mut Self {
    526         match self.claim_generator_info.as_mut() {
    527             Some(cgi) => cgi.push(info),
    528             None => self.claim_generator_info = Some([info].to_vec()),
    529         }
    530         self
    531     }
    532 
    533     pub fn claim_generator_info(&self) -> Option<&[ClaimGeneratorInfo]> {
    534         self.claim_generator_info.as_deref()
    535     }
    536 
    537     pub fn add_claim_generator_hint(&mut self, hint_key: &str, hint_value: Value) {
    538         if self.claim_generator_hints.is_none() {
    539             self.claim_generator_hints = Some(HashMap::new());
    540         }
    541 
    542         if let Some(map) = &mut self.claim_generator_hints {
    543             // if the key is already there do we need to merge the new value, so get its value
    544             let curr_val = match hint_key {
    545                 // keys where new values should be merges
    546                 GH_UA | GH_FULL_VERSION_LIST => {
    547                     if let Some(curr_ch_ua) = map.get(hint_key) {
    548                         curr_ch_ua.as_str().map(|curr_val| curr_val.to_owned())
    549                     } else {
    550                         None
    551                     }
    552                 }
    553                 _ => None,
    554             };
    555 
    556             // had an existing value so merge
    557             if let Some(curr_val) = curr_val {
    558                 if let Some(append_val) = hint_value.as_str() {
    559                     map.insert(
    560                         hint_key.to_string(),
    561                         Value::String(format!("{curr_val}, {append_val}")),
    562                     );
    563                 }
    564                 return;
    565             }
    566 
    567             // all other keys treat as replacement
    568             map.insert(hint_key.to_string(), hint_value);
    569         }
    570     }
    571 
    572     pub const fn get_claim_generator_hint_map(&self) -> Option<&HashMap<String, Value>> {
    573         self.claim_generator_hints.as_ref()
    574     }
    575 
    576     pub fn calc_assertion_box_hash(
    577         label: &str,
    578         assertion: &Assertion,
    579         salt: Option<Vec<u8>>,
    580         alg: &str,
    581     ) -> Result<Vec<u8>> {
    582         // Grab assertion data object.
    583         let d = assertion.decode_data();
    584 
    585         let mut hash_bytes = Vec::with_capacity(2048);
    586 
    587         match d {
    588             AssertionData::Json(_) => {
    589                 let mut json_data = CAIJSONAssertionBox::new(label);
    590                 json_data.add_json(assertion.data().to_vec());
    591                 if let Some(salt) = salt {
    592                     json_data.set_salt(salt)?;
    593                 }
    594                 json_data.super_box().write_box_payload(&mut hash_bytes)?;
    595             }
    596             AssertionData::Binary(_) => {
    597                 // TODO: Handle other binary box types if needed.
    598                 let mut data = JumbfEmbeddedFileBox::new(label);
    599                 data.add_data(assertion.data().to_vec(), assertion.mime_type(), None);
    600                 if let Some(salt) = salt {
    601                     data.set_salt(salt)?;
    602                 }
    603                 data.super_box().write_box_payload(&mut hash_bytes)?;
    604             }
    605             AssertionData::Cbor(_) => {
    606                 let mut cbor_data = CAICBORAssertionBox::new(label);
    607                 cbor_data.add_cbor(assertion.data().to_vec());
    608                 if let Some(salt) = salt {
    609                     cbor_data.set_salt(salt)?;
    610                 }
    611                 cbor_data.super_box().write_box_payload(&mut hash_bytes)?;
    612             }
    613             AssertionData::Uuid(uuid_str, _) => {
    614                 let mut data = CAIUUIDAssertionBox::new(label);
    615                 data.add_uuid(uuid_str, assertion.data().to_vec())?;
    616                 if let Some(salt) = salt {
    617                     data.set_salt(salt)?;
    618                 }
    619                 data.super_box().write_box_payload(&mut hash_bytes)?;
    620             }
    621         }
    622 
    623         Ok(hash_by_alg(alg, &hash_bytes, None))
    624     }
    625 
    626     /// Add an assertion to this claim and verify
    627     pub fn add_assertion(
    628         &mut self,
    629         assertion_builder: &impl AssertionBase,
    630     ) -> Result<C2PAAssertion> {
    631         self.add_assertion_with_salt(assertion_builder, NO_SALT)
    632     }
    633 
    634     /// Add an assertion to this claim and verify with a salted assertion store
    635     /// This version should be used if the assertion may be redacted for addition protection.
    636     pub fn add_assertion_with_salt(
    637         &mut self,
    638         assertion_builder: &impl AssertionBase,
    639         salt_generator: &impl SaltGenerator,
    640     ) -> Result<C2PAAssertion> {
    641         // make sure the assertion is valid
    642         let assertion = assertion_builder.to_assertion()?;
    643 
    644         // Update label if there are multiple instances of
    645         // the same claim type.
    646         let as_label = self.make_assertion_instance_label(assertion.label().as_ref());
    647 
    648         // Get salted hash of the assertion's contents.
    649         let salt = salt_generator.generate_salt();
    650 
    651         let hash = Claim::calc_assertion_box_hash(&as_label, &assertion, salt.clone(), self.alg())?;
    652 
    653         // Build hash link.
    654         let link = jumbf::labels::to_assertion_uri(self.label(), &as_label);
    655         let link_relative = jumbf::labels::to_relative_uri(&link);
    656 
    657         let mut c2pa_assertion = C2PAAssertion::new(link_relative, None, &hash);
    658         c2pa_assertion.add_salt(salt.clone());
    659 
    660         // Add to assertion store.
    661         let (_l, instance) = Claim::assertion_label_from_link(&as_label);
    662         let ca = ClaimAssertion::new(assertion, instance, &hash, self.alg(), salt);
    663         self.assertion_store.push(ca);
    664         self.assertions.push(c2pa_assertion.clone());
    665 
    666         Ok(c2pa_assertion)
    667     }
    668 
    669     // Add a new DataBox and return the HashedURI reference
    670     pub fn add_databox(
    671         &mut self,
    672         format: &str,
    673         data: Vec<u8>,
    674         data_types: Option<Vec<AssetType>>,
    675     ) -> Result<HashedUri> {
    676         // create data box
    677         let new_db = DataBox {
    678             format: format.to_string(),
    679             data,
    680             data_types,
    681         };
    682 
    683         // serialize to cbor
    684         let db_cbor = serde_cbor::to_vec(&new_db).map_err(|_err| Error::AssertionEncoding)?;
    685 
    686         // get the index for the new assertion
    687         let mut index = 0;
    688         for (uri, _db) in &self.data_boxes {
    689             let (_l, i) = Claim::assertion_label_from_link(&uri.url());
    690             if i >= index {
    691                 index = i + 1;
    692             }
    693         }
    694 
    695         let label = Claim::label_with_instance(DATABOX, index);
    696         let link = jumbf::labels::to_databox_uri(self.label(), &label);
    697 
    698         // salt box for 1.2 VC redaction support
    699         let ds = DefaultSalt::default();
    700         let salt = ds.generate_salt();
    701 
    702         // assertion JUMBF box hash for 1.2 validation
    703         let assertion = Assertion::from_data_cbor(&label, &db_cbor);
    704         let hash = Claim::calc_assertion_box_hash(&label, &assertion, salt.clone(), self.alg())?;
    705 
    706         let mut databox_uri = C2PAAssertion::new(link, Some(self.alg().to_string()), &hash);
    707         databox_uri.add_salt(salt);
    708 
    709         // add databox to databox store
    710         self.data_boxes.push((databox_uri.clone(), new_db));
    711 
    712         Ok(databox_uri)
    713     }
    714 
    715     pub(crate) const fn databoxes(&self) -> &Vec<(HashedUri, DataBox)> {
    716         &self.data_boxes
    717     }
    718 
    719     pub fn find_databox(&self, uri: &str) -> Option<&DataBox> {
    720         self.data_boxes
    721             .iter()
    722             .find(|(h, _d)| h.url() == uri)
    723             .map(|(_sh, data_box)| data_box)
    724     }
    725 
    726     /// Load known VC with optional salt
    727     pub(crate) fn put_data_box(
    728         &mut self,
    729         label: &str,
    730         databox_cbor: &[u8],
    731         salt: Option<Vec<u8>>,
    732     ) -> Result<()> {
    733         let link = jumbf::labels::to_databox_uri(self.label(), label);
    734 
    735         // assertion JUMBF box hash for 1.2 validation
    736         let assertion = Assertion::from_data_cbor(label, databox_cbor);
    737         let hash = Claim::calc_assertion_box_hash(label, &assertion, salt.clone(), self.alg())?;
    738 
    739         let mut uri = C2PAAssertion::new(link, Some(self.alg().to_string()), &hash);
    740         uri.add_salt(salt);
    741 
    742         let db: DataBox =
    743             serde_cbor::from_slice(databox_cbor).map_err(|_err| Error::AssertionEncoding)?;
    744 
    745         // add data box  to data box store
    746         self.data_boxes.push((uri, db));
    747 
    748         Ok(())
    749     }
    750 
    751     pub fn get_data_box(&self, uri: &str) -> Option<&DataBox> {
    752         // normalize uri
    753         let normalized_uri = if let Some(manifest) = manifest_label_from_uri(uri) {
    754             if manifest != self.label() {
    755                 return None;
    756             }
    757             uri.to_owned()
    758         } else {
    759             // make a full path
    760             if let Some(box_name) = box_name_from_uri(uri) {
    761                 to_databox_uri(self.label(), &box_name)
    762             } else {
    763                 return None;
    764             }
    765         };
    766 
    767         self.data_boxes.iter().find_map(|x| {
    768             if x.0.url() == normalized_uri {
    769                 Some(&x.1)
    770             } else {
    771                 None
    772             }
    773         })
    774     }
    775 
    776     pub(crate) fn vc_id(vc_json: &str) -> Result<String> {
    777         let vc: Value =
    778             serde_json::from_str(vc_json).map_err(|_err| Error::VerifiableCredentialInvalid)?; // check for json validity
    779 
    780         let credential_subject = vc
    781             .get("credentialSubject")
    782             .ok_or(Error::VerifiableCredentialInvalid)?;
    783         let id = credential_subject
    784             .get("id")
    785             .ok_or(Error::VerifiableCredentialInvalid)?
    786             .as_str()
    787             .ok_or(Error::VerifiableCredentialInvalid)?;
    788 
    789         Ok(id.to_string())
    790     }
    791 
    792     /// Add a verifiable credential to vc store and return a JUMBF URI
    793     /// the credential json must contain "credentialsSubject" object like:
    794     /// ```json
    795     /// "credentialSubject": {
    796     ///    "id": "did:nppa:eb1bb9934d9896a374c384521410c7f14",
    797     ///    "name": "Bob Ross",
    798     ///    "memberOf": "https://nppa.org/"
    799     ///    },
    800     /// ```
    801     // the "id" value will be used as the label in the vcstore
    802     pub fn add_verifiable_credential(&mut self, vc_json: &str) -> Result<HashedUri> {
    803         let id = Claim::vc_id(vc_json)?;
    804         let credential = AssertionData::Json(vc_json.to_string());
    805 
    806         let link = jumbf::labels::to_verifiable_credential_uri(self.label(), &id);
    807 
    808         // salt box for 1.2 VC redaction support
    809         let ds = DefaultSalt::default();
    810         let salt = ds.generate_salt();
    811 
    812         // assertion JUMBF box hash for 1.2 validation
    813         let assertion = Assertion::from_data_json(&id, vc_json.as_bytes())?;
    814         let hash = Claim::calc_assertion_box_hash(&id, &assertion, salt.clone(), self.alg())?;
    815 
    816         let mut c2pa_assertion = C2PAAssertion::new(link, Some(self.alg().to_string()), &hash);
    817         c2pa_assertion.add_salt(salt);
    818 
    819         // add credential to vcstore
    820         self.vc_store.push((c2pa_assertion.clone(), credential));
    821 
    822         Ok(c2pa_assertion)
    823     }
    824 
    825     /// Load known VC with optional salt
    826     pub(crate) fn put_verifiable_credential(
    827         &mut self,
    828         vc_json: &str,
    829         salt: Option<Vec<u8>>,
    830     ) -> Result<()> {
    831         let id = Claim::vc_id(vc_json)?;
    832         let credential = AssertionData::Json(vc_json.to_string());
    833 
    834         let link = jumbf::labels::to_verifiable_credential_uri(self.label(), &id);
    835 
    836         // assertion JUMBF box hash for 1.2 validation
    837         let assertion = Assertion::from_data_json(&id, vc_json.as_bytes())?;
    838         let hash = Claim::calc_assertion_box_hash(&id, &assertion, salt.clone(), self.alg())?;
    839 
    840         let mut c2pa_assertion = C2PAAssertion::new(link, Some(self.alg().to_string()), &hash);
    841         c2pa_assertion.add_salt(salt);
    842 
    843         // add credential to vcstore
    844         self.vc_store.push((c2pa_assertion, credential));
    845 
    846         Ok(())
    847     }
    848 
    849     pub fn get_verifiable_credentials(&self) -> Vec<&AssertionData> {
    850         self.vc_store.iter().map(|t| &t.1).collect::<Vec<_>>()
    851     }
    852 
    853     pub const fn get_verifiable_credentials_store(&self) -> &Vec<(HashedUri, AssertionData)> {
    854         &self.vc_store
    855     }
    856 
    857     /// Add directly to store during a reload of a claim
    858     pub(crate) fn put_assertion_store(&mut self, assertion: ClaimAssertion) {
    859         self.assertion_store.push(assertion);
    860     }
    861 
    862     // Patch an existing assertion with new contents.
    863     //
    864     // `replace_with` should match in name and size of an existing assertion.
    865     fn update_assertion<MatchFn, PatchFn>(
    866         &mut self,
    867         replace_with: Assertion,
    868         match_fn: MatchFn,
    869         patch_fn: PatchFn,
    870     ) -> Result<()>
    871     where
    872         MatchFn: Fn(&ClaimAssertion) -> bool,
    873         PatchFn: FnOnce(&ClaimAssertion, Assertion) -> Result<Assertion>,
    874     {
    875         // Find the assertion that should be replaced.
    876         let Some(ref mut target_assertion) = self
    877             .assertion_store
    878             .iter_mut()
    879             .find(|ca| Assertion::assertions_eq(&replace_with, ca.assertion()) && match_fn(ca))
    880         else {
    881             return Err(Error::NotFound);
    882         };
    883 
    884         // Save off copy of original hash to cross-check before
    885         // replacing it.
    886         let original_hash = target_assertion.hash().to_vec();
    887 
    888         // Give caller a chance to patch/replace the assertion.
    889         let replace_with = patch_fn(target_assertion, replace_with)?;
    890 
    891         // Calculate new hash, given new content.
    892         let replacement_hash = Claim::calc_assertion_box_hash(
    893             &target_assertion.label(),
    894             &replace_with,
    895             target_assertion.salt().clone(),
    896             target_assertion.hash_alg(),
    897         )?;
    898 
    899         target_assertion.update_assertion(replace_with, replacement_hash)?;
    900 
    901         let target_label = target_assertion.label();
    902         let target_hash = target_assertion.hash();
    903 
    904         // Replace the existing hash in the hashed URI reference
    905         // with the newly-calculated hash.
    906         let Some(f) = self
    907             .assertions
    908             .iter_mut()
    909             .find(|f| f.url().contains(&target_label) && vec_compare(&f.hash(), &original_hash))
    910         else {
    911             return Err(Error::NotFound);
    912         };
    913 
    914         // Replace existing hash with newly-calculated hash.
    915         f.update_hash(target_hash.to_vec());
    916         Ok(())
    917     }
    918 
    919     // Crate private function to allow for patching a data hash with final contents.
    920     pub(crate) fn update_data_hash(&mut self, mut data_hash: DataHash) -> Result<()> {
    921         let dh_name = data_hash.name.clone();
    922 
    923         self.update_assertion(
    924             data_hash.to_assertion()?,
    925             |ca: &ClaimAssertion| {
    926                 if let Ok(dh) = DataHash::from_assertion(ca.assertion()) {
    927                     dh.name == dh_name
    928                 } else {
    929                     false
    930                 }
    931             },
    932             |target_assertion: &ClaimAssertion, _: Assertion| {
    933                 let original_len = target_assertion.assertion().data().len();
    934                 data_hash.pad_to_size(original_len)?;
    935                 data_hash.to_assertion()
    936             },
    937         )
    938     }
    939 
    940     // Crate private function to allow for patching a BMFF hash with final contents.
    941     pub(crate) fn update_bmff_hash(&mut self, bmff_hash: BmffHash) -> Result<()> {
    942         self.replace_assertion(bmff_hash.to_assertion()?)
    943     }
    944 
    945     // Patch an existing assertion with new contents.
    946     //
    947     // `replace_with` should match in name and size of an existing assertion.
    948     pub(crate) fn replace_assertion(&mut self, replace_with: Assertion) -> Result<()> {
    949         self.update_assertion(
    950             replace_with,
    951             |_: &ClaimAssertion| true,
    952             |_: &ClaimAssertion, a: Assertion| Ok(a),
    953         )
    954     }
    955 
    956     /// Redact an assertion from a prior claim.
    957     /// This will remove the assertion from the JUMBF
    958     fn redact_assertion(&mut self, assertion_uri: &str) -> Result<()> {
    959         // cannot redact action assertions per the spec
    960         let (label, _instance) = Claim::assertion_label_from_link(assertion_uri);
    961         if label == labels::ACTIONS {
    962             return Err(Error::AssertionInvalidRedaction);
    963         }
    964 
    965         // delete assertion
    966         if let Some(index) = self
    967             .assertion_store
    968             .iter()
    969             .position(|x| assertion_uri.contains(&x.label()))
    970         {
    971             self.assertion_store.remove(index);
    972             Ok(())
    973         } else {
    974             Err(Error::AssertionInvalidRedaction)
    975         }
    976     }
    977 
    978     /// Return a hash of this claim.
    979     pub fn hash(&self) -> Vec<u8> {
    980         match self.data() {
    981             Ok(claim_data) => hash_by_alg(self.alg(), &claim_data, None),
    982             Err(_) => Vec::new(), //  should never happen bug if it does just give no hash
    983         }
    984     }
    985 
    986     /// Return the signing date and time for this claim, if there is one.
    987     pub fn signing_time(&self) -> Option<DateTime<Utc>> {
    988         if let Some(validation_data) = self.signature_info() {
    989             validation_data.date
    990         } else {
    991             None
    992         }
    993     }
    994 
    995     /// Return the signing issuer for this claim, if there is one.
    996     pub fn signing_issuer(&self) -> Option<String> {
    997         if let Some(validation_data) = self.signature_info() {
    998             validation_data.issuer_org
    999         } else {
   1000             None
   1001         }
   1002     }
   1003 
   1004     /// Return the cert's serial number, if there is one.
   1005     pub fn signing_cert_serial(&self) -> Option<String> {
   1006         self.signature_info()
   1007             .and_then(|validation_info| validation_info.cert_serial_number)
   1008             .map(|serial| serial.to_string())
   1009     }
   1010 
   1011     /// Return information about the signature
   1012     pub fn signature_info(&self) -> Option<ValidationInfo> {
   1013         let sig = self.signature_val();
   1014         let data = self.data().ok()?;
   1015         let mut validation_log = OneShotStatusTracker::new();
   1016 
   1017         Some(get_signing_info(sig, &data, &mut validation_log))
   1018     }
   1019 
   1020     /// Verify claim signature, assertion store and asset hashes
   1021     /// claim - claim to be verified
   1022     /// asset_bytes - reference to bytes of the asset
   1023     pub(crate) async fn verify_claim_async<'a>(
   1024         claim: &Claim,
   1025         asset_data: &mut ClaimAssetData<'_>,
   1026         is_provenance: bool,
   1027         th: &dyn TrustHandlerConfig,
   1028         validation_log: &mut impl StatusTracker,
   1029     ) -> Result<()> {
   1030         // Parse COSE signed data (signature) and validate it.
   1031         let sig = claim.signature_val().clone();
   1032         let additional_bytes: Vec<u8> = Vec::new();
   1033         let claim_data = claim.data()?;
   1034 
   1035         // make sure signature manifest if present points to this manifest
   1036         let sig_box_err = match jumbf::labels::manifest_label_from_uri(&claim.signature) {
   1037             Some(signature_url) if signature_url != claim.label() => true,
   1038             _ => {
   1039                 jumbf::labels::box_name_from_uri(&claim.signature).unwrap_or_default()
   1040                     != jumbf::labels::SIGNATURE
   1041             } // relative signature box
   1042         };
   1043 
   1044         if sig_box_err {
   1045             let log_item = log_item!(
   1046                 claim.signature_uri(),
   1047                 "signature missing",
   1048                 "verify_claim_async"
   1049             )
   1050             .error(Error::ClaimMissingSignatureBox)
   1051             .validation_status(validation_status::CLAIM_SIGNATURE_MISSING);
   1052 
   1053             validation_log.log(log_item, Some(Error::ClaimMissingSignatureBox))?;
   1054         }
   1055 
   1056         let verified = verify_cose_async(
   1057             sig,
   1058             claim_data,
   1059             additional_bytes,
   1060             !is_provenance,
   1061             th,
   1062             validation_log,
   1063         )
   1064         .await;
   1065         Claim::verify_internal(claim, asset_data, is_provenance, verified, validation_log)
   1066     }
   1067 
   1068     /// Verify claim signature, assertion store and asset hashes
   1069     /// claim - claim to be verified
   1070     /// asset_bytes - reference to bytes of the asset
   1071     pub(crate) fn verify_claim(
   1072         claim: &Claim,
   1073         asset_data: &mut ClaimAssetData<'_>,
   1074         is_provenance: bool,
   1075         th: &dyn TrustHandlerConfig,
   1076         validation_log: &mut impl StatusTracker,
   1077     ) -> Result<()> {
   1078         // Parse COSE signed data (signature) and validate it.
   1079         let sig = claim.signature_val();
   1080         let additional_bytes: Vec<u8> = Vec::new();
   1081 
   1082         // make sure signature manifest if present points to this manifest
   1083         let sig_box_err = match jumbf::labels::manifest_label_from_uri(&claim.signature) {
   1084             Some(signature_url) if signature_url != claim.label() => true,
   1085             _ => {
   1086                 jumbf::labels::box_name_from_uri(&claim.signature).unwrap_or_default()
   1087                     != jumbf::labels::SIGNATURE
   1088             } // relative signature box
   1089         };
   1090 
   1091         if sig_box_err {
   1092             let log_item = log_item!(claim.signature_uri(), "signature missing", "verify_claim")
   1093                 .error(Error::ClaimMissingSignatureBox)
   1094                 .validation_status(validation_status::CLAIM_SIGNATURE_MISSING);
   1095             validation_log.log(log_item, Some(Error::ClaimMissingSignatureBox))?;
   1096         }
   1097 
   1098         let data = if let Some(ref original_bytes) = claim.original_bytes {
   1099             original_bytes
   1100         } else {
   1101             return Err(Error::ClaimDecoding);
   1102         };
   1103 
   1104         let verified = verify_cose(
   1105             sig,
   1106             data,
   1107             &additional_bytes,
   1108             !is_provenance,
   1109             th,
   1110             validation_log,
   1111         );
   1112 
   1113         Claim::verify_internal(claim, asset_data, is_provenance, verified, validation_log)
   1114     }
   1115 
   1116     /// Get the signing certificate chain as PEM bytes
   1117     pub fn get_cert_chain(&self) -> Result<Vec<u8>> {
   1118         let sig = self.signature_val();
   1119         let data = self.data()?;
   1120         let mut validation_log = OneShotStatusTracker::new();
   1121 
   1122         let vi = get_signing_info(sig, &data, &mut validation_log);
   1123 
   1124         Ok(vi.cert_chain)
   1125     }
   1126 
   1127     fn verify_internal(
   1128         claim: &Claim,
   1129         asset_data: &mut ClaimAssetData<'_>,
   1130         is_provenance: bool,
   1131         verified: Result<ValidationInfo>,
   1132         validation_log: &mut impl StatusTracker,
   1133     ) -> Result<()> {
   1134         const UNNAMED: &str = "unnamed";
   1135         let default_str = |s: &String| s.clone();
   1136 
   1137         match verified {
   1138             Ok(vi) => {
   1139                 if !vi.validated {
   1140                     let log_item = log_item!(
   1141                         claim.signature_uri(),
   1142                         "claim signature is not valid",
   1143                         "verify_internal"
   1144                     )
   1145                     .error(Error::CoseSignature)
   1146                     .validation_status(validation_status::CLAIM_SIGNATURE_MISMATCH);
   1147                     validation_log.log(log_item, Some(Error::CoseSignature))?;
   1148                 } else {
   1149                     let log_item = log_item!(
   1150                         claim.signature_uri(),
   1151                         "claim signature valid",
   1152                         "verify_internal"
   1153                     )
   1154                     .validation_status(validation_status::CLAIM_SIGNATURE_VALIDATED);
   1155                     validation_log.log_silent(log_item);
   1156                 }
   1157             }
   1158             Err(parse_err) => {
   1159                 let log_item = log_item!(
   1160                     claim.signature_uri(),
   1161                     "claim signature is not valid",
   1162                     "verify_internal"
   1163                 )
   1164                 .error(parse_err)
   1165                 .validation_status(validation_status::CLAIM_SIGNATURE_MISMATCH);
   1166                 validation_log.log(log_item, Some(Error::CoseSignature))?;
   1167             }
   1168         };
   1169 
   1170         // check for self redacted assertions and illegal redactions
   1171         if let Some(redactions) = claim.redactions() {
   1172             for r in redactions {
   1173                 let r_manifest = jumbf::labels::manifest_label_from_uri(r)
   1174                     .ok_or(Error::AssertionInvalidRedaction)?;
   1175                 if claim.label().contains(&r_manifest) {
   1176                     let log_item = log_item!(
   1177                         claim.uri(),
   1178                         "claim contains self redaction",
   1179                         "verify_internal"
   1180                     )
   1181                     .error(Error::ClaimSelfRedact)
   1182                     .validation_status(validation_status::ASSERTION_SELF_REDACTED);
   1183                     validation_log.log(log_item, Some(Error::ClaimSelfRedact))?;
   1184                 }
   1185 
   1186                 if r.contains(assertions::labels::ACTIONS) {
   1187                     let log_item = log_item!(
   1188                         claim.uri(),
   1189                         "redaction of action assertions disallowed",
   1190                         "verify_internal"
   1191                     )
   1192                     .error(Error::ClaimDisallowedRedaction)
   1193                     .validation_status(validation_status::ACTION_ASSERTION_REDACTED);
   1194                     validation_log.log(log_item, Some(Error::ClaimDisallowedRedaction))?;
   1195                 }
   1196             }
   1197         }
   1198 
   1199         // make sure UpdateManifests do not contain actions
   1200         if claim.update_manifest() && claim.label().contains(assertions::labels::ACTIONS) {
   1201             let log_item = log_item!(
   1202                 claim.uri(),
   1203                 "update manifests cannot contain actions",
   1204                 "verify_internal"
   1205             )
   1206             .error(Error::UpdateManifestInvalid)
   1207             .validation_status(validation_status::MANIFEST_UPDATE_INVALID);
   1208             validation_log.log(log_item, Some(Error::UpdateManifestInvalid))?;
   1209         }
   1210 
   1211         // verify assertion structure comparing hashes from assertion list to contents of assertion store
   1212         for assertion in claim.assertions() {
   1213             let (label, instance) = Claim::assertion_label_from_link(&assertion.url());
   1214             match claim.get_claim_assertion(&label, instance) {
   1215                 // get the assertion if label and hash match
   1216                 Some(ca) => {
   1217                     if !vec_compare(ca.hash(), &assertion.hash()) {
   1218                         let log_item = log_item!(
   1219                             assertion.url(),
   1220                             format!("hash does not match assertion data: {}", assertion.url()),
   1221                             "verify_internal"
   1222                         )
   1223                         .error(Error::HashMismatch(format!(
   1224                             "Assertion hash failure: {}",
   1225                             assertion.url()
   1226                         )))
   1227                         .validation_status(validation_status::ASSERTION_HASHEDURI_MISMATCH);
   1228                         validation_log.log(
   1229                             log_item,
   1230                             Some(Error::HashMismatch(format!(
   1231                                 "Assertion hash failure: {}",
   1232                                 assertion.url()
   1233                             ))),
   1234                         )?;
   1235                     } else {
   1236                         let log_item = log_item!(
   1237                             assertion.url(),
   1238                             format!("hashed uri matched: {}", assertion.url()),
   1239                             "verify_internal"
   1240                         )
   1241                         .validation_status(validation_status::ASSERTION_HASHEDURI_MATCH);
   1242                         validation_log.log_silent(log_item);
   1243                     }
   1244                 }
   1245                 None => {
   1246                     let log_item = log_item!(
   1247                         assertion.url(),
   1248                         format!("cannot find matching assertion: {}", assertion.url()),
   1249                         "verify_internal"
   1250                     )
   1251                     .error(Error::AssertionMissing {
   1252                         url: assertion.url(),
   1253                     })
   1254                     .validation_status(validation_status::ASSERTION_MISSING);
   1255                     validation_log.log(
   1256                         log_item,
   1257                         Some(Error::AssertionMissing {
   1258                             url: assertion.url(),
   1259                         }),
   1260                     )?;
   1261                 }
   1262             }
   1263         }
   1264 
   1265         // verify data hashes for provenance claims
   1266         if is_provenance {
   1267             // must have at least one hard binding for normal manifests
   1268             if claim.hash_assertions().is_empty() && !claim.update_manifest() {
   1269                 let log_item = log_item!(
   1270                     &claim.uri(),
   1271                     "claim missing data binding",
   1272                     "verify_internal"
   1273                 )
   1274                 .error(Error::ClaimMissingHardBinding)
   1275                 .validation_status(validation_status::HARD_BINDINGS_MISSING);
   1276                 validation_log.log(log_item, Some(Error::ClaimMissingHardBinding))?;
   1277             }
   1278 
   1279             // update manifests cannot have data hashes
   1280             if !claim.hash_assertions().is_empty() && claim.update_manifest() {
   1281                 let log_item = log_item!(
   1282                     &claim.uri(),
   1283                     "update manifests cannot contain data hash assertions",
   1284                     "verify_internal"
   1285                 )
   1286                 .error(Error::UpdateManifestInvalid)
   1287                 .validation_status(validation_status::MANIFEST_UPDATE_INVALID);
   1288                 validation_log.log(log_item, Some(Error::UpdateManifestInvalid))?;
   1289             }
   1290 
   1291             for hash_binding_assertion in claim.hash_assertions() {
   1292                 if hash_binding_assertion.label_root() == DataHash::LABEL {
   1293                     let dh = DataHash::from_assertion(hash_binding_assertion)?;
   1294                     let name = dh.name.as_ref().map_or(UNNAMED.to_string(), default_str);
   1295                     if !dh.is_remote_hash() {
   1296                         // only verify local hashes here
   1297                         let hash_result = match asset_data {
   1298                             #[cfg(feature = "file_io")]
   1299                             ClaimAssetData::Path(asset_path) => {
   1300                                 dh.verify_hash(asset_path, Some(claim.alg()))
   1301                             }
   1302                             ClaimAssetData::Bytes(asset_bytes, _) => {
   1303                                 dh.verify_in_memory_hash(asset_bytes, Some(claim.alg()))
   1304                             }
   1305                             ClaimAssetData::Stream(stream_data, _) => {
   1306                                 dh.verify_stream_hash(*stream_data, Some(claim.alg()))
   1307                             }
   1308                             _ => return Err(Error::UnsupportedType), /* this should never happen (coding error) */
   1309                         };
   1310 
   1311                         match hash_result {
   1312                             Ok(_a) => {
   1313                                 let log_item = log_item!(
   1314                                     claim.assertion_uri(&hash_binding_assertion.label()),
   1315                                     "data hash valid",
   1316                                     "verify_internal"
   1317                                 )
   1318                                 .validation_status(validation_status::ASSERTION_DATAHASH_MATCH);
   1319                                 validation_log.log_silent(log_item);
   1320 
   1321                                 continue;
   1322                             }
   1323                             Err(e) => {
   1324                                 let log_item = log_item!(
   1325                                     claim.assertion_uri(&hash_binding_assertion.label()),
   1326                                     format!("asset hash error, name: {name}, error: {e}"),
   1327                                     "verify_internal"
   1328                                 )
   1329                                 .error(Error::HashMismatch(format!("Asset hash failure: {e}")))
   1330                                 .validation_status(validation_status::ASSERTION_DATAHASH_MISMATCH);
   1331 
   1332                                 validation_log.log(
   1333                                     log_item,
   1334                                     Some(Error::HashMismatch(format!("Asset hash failure: {e}"))),
   1335                                 )?;
   1336                             }
   1337                         }
   1338                     }
   1339                 } else if hash_binding_assertion.label_root() == BmffHash::LABEL {
   1340                     // handle BMFF data hashes
   1341                     let dh = BmffHash::from_assertion(hash_binding_assertion)?;
   1342 
   1343                     let name = dh.name().map_or("unnamed".to_string(), default_str);
   1344 
   1345                     let hash_result = match asset_data {
   1346                         #[cfg(feature = "file_io")]
   1347                         ClaimAssetData::Path(asset_path) => {
   1348                             dh.verify_hash(asset_path, Some(claim.alg()))
   1349                         }
   1350                         ClaimAssetData::Bytes(asset_bytes, _) => {
   1351                             dh.verify_in_memory_hash(asset_bytes, Some(claim.alg()))
   1352                         }
   1353                         ClaimAssetData::Stream(stream_data, _) => {
   1354                             dh.verify_stream_hash(*stream_data, Some(claim.alg()))
   1355                         }
   1356                         ClaimAssetData::StreamFragment(initseg_data, fragment_data, _) => dh
   1357                             .verify_stream_segment(
   1358                                 *initseg_data,
   1359                                 *fragment_data,
   1360                                 Some(claim.alg()),
   1361                             ),
   1362                     };
   1363 
   1364                     match hash_result {
   1365                         Ok(_a) => {
   1366                             let log_item = log_item!(
   1367                                 claim.assertion_uri(&hash_binding_assertion.label()),
   1368                                 "data hash valid",
   1369                                 "verify_internal"
   1370                             )
   1371                             .validation_status(validation_status::ASSERTION_BMFFHASH_MATCH);
   1372                             validation_log.log_silent(log_item);
   1373 
   1374                             continue;
   1375                         }
   1376                         Err(e) => {
   1377                             let log_item = log_item!(
   1378                                 claim.assertion_uri(&hash_binding_assertion.label()),
   1379                                 format!("asset hash error, name: {name}, error: {e}"),
   1380                                 "verify_internal"
   1381                             )
   1382                             .error(Error::HashMismatch(format!("Asset hash failure: {e}")))
   1383                             .validation_status(validation_status::ASSERTION_BMFFHASH_MISMATCH);
   1384 
   1385                             validation_log.log(
   1386                                 log_item,
   1387                                 Some(Error::HashMismatch(format!("Asset hash failure: {e}"))),
   1388                             )?;
   1389                         }
   1390                     }
   1391                 } else if hash_binding_assertion.label_root() == BoxHash::LABEL {
   1392                     // box hash case
   1393                     // handle BMFF data hashes
   1394                     let bh = BoxHash::from_assertion(hash_binding_assertion)?;
   1395 
   1396                     let hash_result = match asset_data {
   1397                         #[cfg(feature = "file_io")]
   1398                         ClaimAssetData::Path(asset_path) => {
   1399                             let box_hash_processor =
   1400                                 crate::jumbf_io::get_assetio_handler_from_path(asset_path)
   1401                                     .ok_or(Error::UnsupportedType)?
   1402                                     .asset_box_hash_ref()
   1403                                     .ok_or(Error::HashMismatch(
   1404                                         "Box hash not supported".to_string(),
   1405                                     ))?;
   1406 
   1407                             bh.verify_hash(asset_path, Some(claim.alg()), box_hash_processor)
   1408                         }
   1409                         ClaimAssetData::Bytes(asset_bytes, asset_type) => {
   1410                             let box_hash_processor = get_assetio_handler(asset_type)
   1411                                 .ok_or(Error::UnsupportedType)?
   1412                                 .asset_box_hash_ref()
   1413                                 .ok_or(Error::HashMismatch(format!(
   1414                                     "Box hash not supported for: {asset_type}"
   1415                                 )))?;
   1416 
   1417                             bh.verify_in_memory_hash(
   1418                                 asset_bytes,
   1419                                 Some(claim.alg()),
   1420                                 box_hash_processor,
   1421                             )
   1422                         }
   1423                         ClaimAssetData::Stream(stream_data, asset_type) => {
   1424                             let box_hash_processor = get_assetio_handler(asset_type)
   1425                                 .ok_or(Error::UnsupportedType)?
   1426                                 .asset_box_hash_ref()
   1427                                 .ok_or(Error::HashMismatch(format!(
   1428                                     "Box hash not supported for: {asset_type}"
   1429                                 )))?;
   1430 
   1431                             bh.verify_stream_hash(
   1432                                 *stream_data,
   1433                                 Some(claim.alg()),
   1434                                 box_hash_processor,
   1435                             )
   1436                         }
   1437                         _ => return Err(Error::UnsupportedType),
   1438                     };
   1439 
   1440                     match hash_result {
   1441                         Ok(_a) => {
   1442                             let log_item = log_item!(
   1443                                 claim.assertion_uri(&hash_binding_assertion.label()),
   1444                                 "data hash valid",
   1445                                 "verify_internal"
   1446                             )
   1447                             .validation_status(validation_status::ASSERTION_BOXHASH_MATCH);
   1448                             validation_log.log_silent(log_item);
   1449 
   1450                             continue;
   1451                         }
   1452                         Err(e) => {
   1453                             let log_item = log_item!(
   1454                                 claim.assertion_uri(&hash_binding_assertion.label()),
   1455                                 format!("asset hash error: {e}"),
   1456                                 "verify_internal"
   1457                             )
   1458                             .error(Error::HashMismatch(format!("Asset hash failure: {e}")))
   1459                             .validation_status(validation_status::ASSERTION_BOXHASH_MISMATCH);
   1460 
   1461                             validation_log.log(
   1462                                 log_item,
   1463                                 Some(Error::HashMismatch(format!("Asset hash failure: {e}"))),
   1464                             )?;
   1465                         }
   1466                     }
   1467                 }
   1468             }
   1469         }
   1470         Ok(())
   1471     }
   1472 
   1473     /// Verify hash against self.  True if match,
   1474     /// false if no match or unsupported
   1475     pub fn verify_hash(&self, hash: &[u8]) -> bool {
   1476         // get hash of self for comparison
   1477         if let Some(ref original_bytes) = self.original_bytes {
   1478             verify_by_alg(self.alg(), hash, original_bytes, None)
   1479         } else if let Ok(claim_data) = self.data() {
   1480             verify_by_alg(self.alg(), hash, &claim_data, None)
   1481         } else {
   1482             false
   1483         }
   1484     }
   1485 
   1486     /// Return list of data hash assertions
   1487     pub fn hash_assertions(&self) -> Vec<&Assertion> {
   1488         let dummy_data = AssertionData::Cbor(Vec::new());
   1489         let dummy_hash = Assertion::new(DataHash::LABEL, None, dummy_data);
   1490         let mut data_hashes = self.assertions_by_type(&dummy_hash);
   1491 
   1492         // add in an BMFF hashes
   1493         let dummy_bmff_data = AssertionData::Cbor(Vec::new());
   1494         let dummy_bmff_hash = Assertion::new(assertions::labels::BMFF_HASH, None, dummy_bmff_data);
   1495         data_hashes.append(&mut self.assertions_by_type(&dummy_bmff_hash));
   1496 
   1497         // add in an box hashes
   1498         let dummy_box_data = AssertionData::Cbor(Vec::new());
   1499         let dummy_box_hash = Assertion::new(assertions::labels::BOX_HASH, None, dummy_box_data);
   1500         data_hashes.append(&mut self.assertions_by_type(&dummy_box_hash));
   1501 
   1502         data_hashes
   1503     }
   1504 
   1505     pub fn bmff_hash_assertions(&self) -> Vec<&Assertion> {
   1506         // add in an BMFF hashes
   1507         let dummy_bmff_data = AssertionData::Cbor(Vec::new());
   1508         let dummy_bmff_hash = Assertion::new(assertions::labels::BMFF_HASH, None, dummy_bmff_data);
   1509         self.assertions_by_type(&dummy_bmff_hash)
   1510     }
   1511 
   1512     pub fn box_hash_assertions(&self) -> Vec<&Assertion> {
   1513         // add in an BMFF hashes
   1514         let dummy_box_data = AssertionData::Cbor(Vec::new());
   1515         let dummy_box_hash = Assertion::new(assertions::labels::BOX_HASH, None, dummy_box_data);
   1516         self.assertions_by_type(&dummy_box_hash)
   1517     }
   1518 
   1519     /// Return list of ingredient assertions. This function
   1520     /// is only useful on committed or loaded claims since ingredients
   1521     /// are resolved at commit time.
   1522     pub fn ingredient_assertions(&self) -> Vec<&Assertion> {
   1523         let dummy_data = AssertionData::Cbor(Vec::new());
   1524         let dummy_ingredient = Assertion::new(labels::INGREDIENT, None, dummy_data);
   1525         self.assertions_by_type(&dummy_ingredient)
   1526     }
   1527 
   1528     /// Return reference to the internal claim assertion store.
   1529     pub const fn claim_assertion_store(&self) -> &Vec<ClaimAssertion> {
   1530         &self.assertion_store
   1531     }
   1532 
   1533     /// Return reference to the internal claim ingredient store.
   1534     /// Used during generation
   1535     pub const fn claim_ingredient_store(&self) -> &HashMap<String, Vec<Claim>> {
   1536         &self.ingredients_store
   1537     }
   1538 
   1539     /// Return reference to the internal claim ingredient store matching this guid.
   1540     /// Used during generation
   1541     pub fn claim_ingredient(&self, claim_guid: &str) -> Option<&Vec<Claim>> {
   1542         self.ingredients_store.get(claim_guid)
   1543     }
   1544 
   1545     /// Adds ingredients, this data will be written out during commit of the Claim
   1546     pub(crate) fn add_ingredient_data(
   1547         &mut self,
   1548         provenance_label: &str,
   1549         mut ingredient: Vec<Claim>,
   1550         redactions_opt: Option<Vec<String>>,
   1551     ) -> Result<()> {
   1552         // redact assertion from incoming ingredients
   1553         if let Some(redactions) = &redactions_opt {
   1554             for redaction in redactions {
   1555                 if let Some(claim) = ingredient
   1556                     .iter_mut()
   1557                     .find(|x| redaction.contains(x.label()))
   1558                 {
   1559                     claim.redact_assertion(redaction)?;
   1560                 } else {
   1561                     return Err(Error::AssertionRedactionNotFound);
   1562                 }
   1563             }
   1564         }
   1565 
   1566         // all have been removed (if necessary) so replace redaction list
   1567         self.redacted_assertions = redactions_opt;
   1568 
   1569         // add ingredients
   1570         self.ingredients_store
   1571             .insert(provenance_label.to_string(), ingredient);
   1572 
   1573         Ok(())
   1574     }
   1575 
   1576     /// List of redactions
   1577     pub const fn redactions(&self) -> Option<&Vec<String>> {
   1578         self.redacted_assertions.as_ref()
   1579     }
   1580 
   1581     /// Return snapshot clone of the claim's assertions.
   1582     pub fn assertion_store(&self) -> Vec<Assertion> {
   1583         self.assertion_store
   1584             .iter()
   1585             .map(|x| x.assertion.clone())
   1586             .collect()
   1587     }
   1588 
   1589     pub fn assertions_by_type(&self, assertion_proto: &Assertion) -> Vec<&Assertion> {
   1590         self.assertion_store
   1591             .iter()
   1592             .filter_map(|x| {
   1593                 if Assertion::assertions_eq(assertion_proto, x.assertion()) {
   1594                     Some(&x.assertion)
   1595                 } else {
   1596                     None
   1597                 }
   1598             })
   1599             .collect()
   1600     }
   1601 
   1602     /// Return reference to the assertions list.
   1603     ///
   1604     /// This list matches item-for-item with the `Assertion`s
   1605     /// stored in the assertion store.
   1606     pub const fn assertions(&self) -> &Vec<C2PAAssertion> {
   1607         &self.assertions
   1608     }
   1609 
   1610     /// Returns the cbor binary value of the claim data.
   1611     /// If this claim was read from a file, returns the exact byte
   1612     /// sequence that was read from the file. If this claim was
   1613     /// constructed locally, contains the claim data that was/will be
   1614     /// generated locally.
   1615     pub fn data(&self) -> Result<Vec<u8>> {
   1616         match self.original_bytes {
   1617             Some(ref ob) => Ok(ob.clone()),
   1618             None => Ok(serde_cbor::ser::to_vec(&self).map_err(|_err| Error::ClaimEncoding)?),
   1619         }
   1620     }
   1621 
   1622     /// Create claim from binary data (not including assertions).
   1623     pub fn from_data(label: &str, data: &[u8]) -> Result<Claim> {
   1624         let mut claim: Claim = serde_cbor::from_slice(data).map_err(|_err| Error::ClaimDecoding)?;
   1625 
   1626         claim.label = label.to_string();
   1627         claim.original_bytes = Some(data.to_owned());
   1628 
   1629         Ok(claim)
   1630     }
   1631 
   1632     /// Generate a JSON representation of the Claim
   1633     /// returns Result as a String
   1634     pub fn to_json(
   1635         &self,
   1636         assertion_store_format: AssertionStoreJsonFormat,
   1637         pretty: bool,
   1638     ) -> Result<String> {
   1639         let mut v = serde_json::to_value(self)?;
   1640 
   1641         match assertion_store_format {
   1642             AssertionStoreJsonFormat::None => {}
   1643             AssertionStoreJsonFormat::KeyValue | AssertionStoreJsonFormat::KeyValueNoBinary => {
   1644                 // add additional data if needed to the assertion store
   1645                 if let Value::Object(ref mut map) = v {
   1646                     // merge the label with the data
   1647                     let mut json_map: Map<String, Value> = Map::new();
   1648                     let iter = self.assertions.iter().zip(&self.assertion_store);
   1649 
   1650                     for (_key, claim_assertion) in iter {
   1651                         let link = claim_assertion.label();
   1652                         let (label, instance) = Self::assertion_label_from_link(&link);
   1653                         let label = Self::label_with_instance(&label, instance);
   1654 
   1655                         match claim_assertion.assertion.decode_data() {
   1656                             AssertionData::Json(x) => {
   1657                                 // json strings
   1658                                 let decoded = serde_json::from_str(x)?;
   1659                                 json_map.insert(label, decoded);
   1660                             }
   1661                             AssertionData::Cbor(x) => {
   1662                                 // some types are not translatable to json so explicitly convert
   1663                                 let buf: Vec<u8> = Vec::new();
   1664                                 let mut from = serde_cbor::Deserializer::from_slice(x);
   1665                                 let mut to = serde_json::Serializer::new(buf);
   1666 
   1667                                 serde_transcode::transcode(&mut from, &mut to)
   1668                                     .map_err(|_err| Error::AssertionEncoding)?;
   1669                                 let buf2 = to.into_inner();
   1670 
   1671                                 let decoded: Value = serde_json::from_slice(&buf2)
   1672                                     .map_err(|_err| Error::AssertionEncoding)?;
   1673 
   1674                                 json_map.insert(label, decoded);
   1675                             }
   1676                             AssertionData::Binary(x) => {
   1677                                 // binary vecs
   1678                                 let d = match assertion_store_format {
   1679                                     AssertionStoreJsonFormat::KeyValue => {
   1680                                         Value::String(base64::encode(x))
   1681                                     }
   1682                                     AssertionStoreJsonFormat::KeyValueNoBinary => {
   1683                                         Value::String("omitted".to_owned())
   1684                                     }
   1685                                     _ => Value::String("".to_owned()),
   1686                                 };
   1687                                 json_map.insert(label, d);
   1688                                 continue;
   1689                             }
   1690                             AssertionData::Uuid(s, x) => {
   1691                                 // binary vecs
   1692                                 let d = match assertion_store_format {
   1693                                     AssertionStoreJsonFormat::KeyValue => {
   1694                                         Value::String(base64::encode(x))
   1695                                     }
   1696                                     AssertionStoreJsonFormat::KeyValueNoBinary => {
   1697                                         Value::String("omitted".to_owned())
   1698                                     }
   1699                                     _ => Value::String("".to_owned()),
   1700                                 };
   1701 
   1702                                 let m = json!({
   1703                                     "uuid": s,
   1704                                     "data": d,
   1705                                 });
   1706 
   1707                                 json_map.insert(label, m);
   1708                                 continue;
   1709                             }
   1710                         }
   1711                     }
   1712                     //let s = serde_json::to_string(&json_map)?;
   1713                     //let as_val = serde_json::from_str(&s)?;
   1714                     let as_val = serde_json::to_value(json_map)?;
   1715                     map.insert("assertion_store".to_string(), as_val);
   1716 
   1717                     // add vcstore
   1718                     map.insert(
   1719                         "vc_store".to_string(),
   1720                         serde_json::to_value(&self.vc_store)?,
   1721                     );
   1722 
   1723                     // add claim label
   1724                     map.insert("label".to_string(), Value::String(self.label.to_string()));
   1725                 }
   1726             }
   1727             AssertionStoreJsonFormat::OrderedList
   1728             | AssertionStoreJsonFormat::OrderedListNoBinary => {
   1729                 // add additional data if needed to the assertion store
   1730                 if let Value::Object(ref mut map) = v {
   1731                     let mut json_vec: Vec<Value> = Vec::new();
   1732 
   1733                     // assertion values
   1734                     for claim_assertion in self.claim_assertion_store() {
   1735                         match claim_assertion.assertion.decode_data() {
   1736                             AssertionData::Json(x) => {
   1737                                 let d: Value = serde_json::from_str(x)
   1738                                     .map_err(|_err| Error::AssertionEncoding)?;
   1739 
   1740                                 let j = JsonOrderedAssertionData {
   1741                                     label: claim_assertion.label().to_owned(),
   1742                                     hash: base64::encode(claim_assertion.hash()),
   1743                                     data: d,
   1744                                     is_binary: false,
   1745                                     mime_type: claim_assertion.assertion.mime_type(),
   1746                                 };
   1747 
   1748                                 let new_val = serde_json::to_value(j)?;
   1749                                 json_vec.push(new_val);
   1750                             }
   1751                             AssertionData::Cbor(x) => {
   1752                                 // some types are not translatable to json so explicitly convert
   1753                                 let buf: Vec<u8> = Vec::new();
   1754                                 let mut from = serde_cbor::Deserializer::from_slice(x);
   1755                                 let mut to = serde_json::Serializer::new(buf);
   1756 
   1757                                 serde_transcode::transcode(&mut from, &mut to)
   1758                                     .map_err(|_err| Error::AssertionEncoding)?;
   1759                                 let buf2 = to.into_inner();
   1760 
   1761                                 let d: Value = serde_json::from_slice(&buf2)
   1762                                     .map_err(|_err| Error::AssertionEncoding)?;
   1763 
   1764                                 let j = JsonOrderedAssertionData {
   1765                                     label: claim_assertion.label().to_owned(),
   1766                                     hash: base64::encode(claim_assertion.hash()),
   1767                                     data: d,
   1768                                     is_binary: false,
   1769                                     mime_type: claim_assertion.assertion.mime_type(),
   1770                                 };
   1771 
   1772                                 let new_val = serde_json::to_value(j)?;
   1773                                 json_vec.push(new_val);
   1774                             }
   1775                             AssertionData::Binary(x) => {
   1776                                 // binary data
   1777                                 let d = match assertion_store_format {
   1778                                     AssertionStoreJsonFormat::OrderedList => {
   1779                                         Value::String(base64::encode(x))
   1780                                     }
   1781                                     AssertionStoreJsonFormat::OrderedListNoBinary => {
   1782                                         Value::String("omitted".to_owned())
   1783                                     }
   1784                                     _ => Value::String("".to_owned()),
   1785                                 };
   1786 
   1787                                 let j = JsonOrderedAssertionData {
   1788                                     label: claim_assertion.label().to_owned(),
   1789                                     hash: base64::encode(claim_assertion.hash()),
   1790                                     data: d,
   1791                                     is_binary: true,
   1792                                     mime_type: claim_assertion.assertion.mime_type(),
   1793                                 };
   1794 
   1795                                 let new_val = serde_json::to_value(j)?;
   1796                                 json_vec.push(new_val);
   1797                             }
   1798                             AssertionData::Uuid(s, x) => {
   1799                                 // binary data
   1800                                 let d = match assertion_store_format {
   1801                                     AssertionStoreJsonFormat::OrderedList => {
   1802                                         Value::String(base64::encode(x))
   1803                                     }
   1804                                     AssertionStoreJsonFormat::OrderedListNoBinary => {
   1805                                         Value::String("omitted".to_owned())
   1806                                     }
   1807                                     _ => Value::String("".to_owned()),
   1808                                 };
   1809 
   1810                                 let m = json!({
   1811                                     "uuid": s,
   1812                                     "data": d,
   1813                                 });
   1814 
   1815                                 let j = JsonOrderedAssertionData {
   1816                                     label: claim_assertion.label().to_owned(),
   1817                                     hash: base64::encode(claim_assertion.hash()),
   1818                                     data: m,
   1819                                     is_binary: true,
   1820                                     mime_type: claim_assertion.assertion.mime_type(),
   1821                                 };
   1822 
   1823                                 let new_val = serde_json::to_value(j)?;
   1824                                 json_vec.push(new_val);
   1825                             }
   1826                         }
   1827                     }
   1828 
   1829                     let as_val = serde_json::to_value(json_vec)?;
   1830                     map.insert("assertion_store".to_string(), as_val);
   1831 
   1832                     // add claim label
   1833                     map.insert("label".to_string(), Value::String(self.label.to_string()));
   1834                 }
   1835             }
   1836         }
   1837 
   1838         if pretty {
   1839             serde_json::to_string_pretty(&v).map_err(|e| e.into())
   1840         } else {
   1841             serde_json::to_string(&v).map_err(|e| e.into())
   1842         }
   1843     }
   1844 
   1845     /// Return the label for this assertion given its link
   1846     pub fn assertion_label_from_link(assertion_link: &str) -> (String, usize) {
   1847         let v = jumbf::labels::to_normalized_uri(assertion_link);
   1848 
   1849         let v2: Vec<&str> = v.split('/').collect();
   1850         if let Some(s) = v2.last() {
   1851             // treat ingredient thumbnails differently ingredient.thumbnail
   1852             if get_thumbnail_type(s) == labels::INGREDIENT_THUMBNAIL {
   1853                 let instance = get_thumbnail_instance(s).unwrap_or(0);
   1854                 let label = match get_thumbnail_image_type(s).as_str() {
   1855                     "none" => get_thumbnail_type(s),
   1856                     image_type => format!("{}.{}", get_thumbnail_type(s), image_type),
   1857                 };
   1858                 (label, instance)
   1859             } else {
   1860                 let label_parts: Vec<&str> = s.split("__").collect();
   1861                 let mut instance: usize = 0;
   1862 
   1863                 if label_parts.len() == 2 {
   1864                     match label_parts[1].parse::<usize>() {
   1865                         Ok(i) => instance = i,
   1866                         _ => instance = 0,
   1867                     }
   1868                 }
   1869 
   1870                 (label_parts[0].to_owned(), instance)
   1871             }
   1872         } else {
   1873             (v2[0].to_owned(), 0)
   1874         }
   1875     }
   1876 
   1877     /// generates label with instance if needed
   1878     pub fn label_with_instance(label: &str, instance: usize) -> String {
   1879         if instance == 0 {
   1880             label.to_string()
   1881         } else if get_thumbnail_type(label) == labels::INGREDIENT_THUMBNAIL {
   1882             let tn_type = get_thumbnail_image_type(label);
   1883             format!("{}__{}.{}", get_thumbnail_type(label), instance, tn_type)
   1884         } else {
   1885             format!("{label}__{instance}")
   1886         }
   1887     }
   1888 
   1889     pub fn assertion_hashed_uri_from_label(&self, assertion_label: &str) -> Option<&C2PAAssertion> {
   1890         self.assertions()
   1891             .iter()
   1892             .find(|hashed_uri| hashed_uri.url().contains(assertion_label))
   1893     }
   1894 
   1895     // Given a proposed label, make a new label that is unique within this
   1896     // assertion store. Typically this is done by adding `__{n}` where `n` is
   1897     // an integer starting from 1. Ingredient thumbnails have special handling.
   1898     fn make_assertion_instance_label(&self, assertion_label: &str) -> String {
   1899         let cnt = self.next_instance(assertion_label);
   1900 
   1901         Claim::label_with_instance(assertion_label, cnt)
   1902     }
   1903 
   1904     /// returns first instance of an assertion whose label and instance match
   1905     pub fn get_assertion(&self, assertion_label: &str, instance: usize) -> Option<&Assertion> {
   1906         let mut iter = self.claim_assertion_store().iter().filter_map(|ca| {
   1907             if ca.label_raw() == assertion_label && ca.instance() == instance {
   1908                 Some(ca.assertion())
   1909             } else {
   1910                 None
   1911             }
   1912         });
   1913 
   1914         iter.next()
   1915     }
   1916 
   1917     /// returns instance of an assertion whose label and instance match
   1918     pub fn get_claim_assertion(
   1919         &self,
   1920         assertion_label: &str,
   1921         instance: usize,
   1922     ) -> Option<&ClaimAssertion> {
   1923         self.claim_assertion_store()
   1924             .iter()
   1925             .find(|ca| ca.label_raw() == assertion_label && ca.instance() == instance)
   1926     }
   1927 
   1928     /// returns hash of an assertion whose label and instance match
   1929     pub fn get_claim_assertion_hash(&self, assertion_label: &str) -> Option<Vec<u8>> {
   1930         let (l, i) = Claim::assertion_label_from_link(assertion_label);
   1931         self.get_claim_assertion(&l, i).map(|a| a.hash().to_vec())
   1932     }
   1933 
   1934     /// Returns how many assertions of this assertion type exist?
   1935     pub fn count_instances(&self, in_label: &str) -> usize {
   1936         let (l, i) = Claim::assertion_label_from_link(in_label);
   1937         let label = Claim::label_with_instance(&l, i);
   1938         self.assertions
   1939             .iter()
   1940             .filter(|assertion| assertion.url().contains(&label))
   1941             .count()
   1942     }
   1943 
   1944     // Get the next highest instance label
   1945     fn next_instance(&self, in_label: &str) -> usize {
   1946         let (label, _) = Claim::assertion_label_from_link(in_label);
   1947         match self
   1948             .assertion_store
   1949             .iter()
   1950             .filter(|&x| x.assertion.label().contains(&label))
   1951             .map(|x| {
   1952                 let (_l, i) = Claim::assertion_label_from_link(&x.label());
   1953                 i
   1954             })
   1955             .max()
   1956         {
   1957             Some(last_instance) => last_instance + 1,
   1958             None => 0,
   1959         }
   1960     }
   1961 
   1962     // Do any assertions of this type exist?
   1963     pub fn has_assertion_type(&self, in_label: &str) -> bool {
   1964         let (label, _) = Claim::assertion_label_from_link(in_label);
   1965 
   1966         self.assertion_store
   1967             .iter()
   1968             .any(|x| x.assertion.label().starts_with(&label))
   1969     }
   1970 
   1971     // Create a JUMBF URI from a claim label.
   1972     pub(crate) fn to_claim_uri(manifest_label: &str) -> String {
   1973         format!(
   1974             "{}/{}",
   1975             jumbf::labels::to_manifest_uri(manifest_label),
   1976             Self::LABEL
   1977         )
   1978     }
   1979 }
   1980 
   1981 #[cfg(not(target_arch = "wasm32"))]
   1982 #[cfg(test)]
   1983 pub mod tests {
   1984     #![allow(clippy::expect_used)]
   1985     #![allow(clippy::unwrap_used)]
   1986 
   1987     use super::*;
   1988     use crate::{resource_store::UriOrResource, utils::test::create_test_claim};
   1989 
   1990     #[test]
   1991     fn test_build_claim() {
   1992         // Create a new claim.
   1993         let mut claim = create_test_claim().expect("create test claim");
   1994 
   1995         // Add a redaction.
   1996         // claim.redact_assertion("as_tp_1/c2pa.location.precise");
   1997 
   1998         // Build claim checking rules.
   1999         claim.build().expect("bad claim");
   2000 
   2001         // Test round-tripping of binary.
   2002         let orig_binary = claim.data().expect("failure returning data");
   2003         let restored_claim =
   2004             Claim::from_data("as_adbe_1", &orig_binary).expect("could not restore from binary");
   2005         let restored_binary = restored_claim.data().expect("failure returning data");
   2006 
   2007         assert_eq!(orig_binary, restored_binary);
   2008         println!("Restored Claim: {restored_claim:?}");
   2009 
   2010         // NOTE: I added a separate mirror of original data because a third-party's
   2011         // JSON serialization could differ from our re-serialization of that same data.
   2012         // When reading claims from assets and verifying signatures of those claims,
   2013         // we need the exact original bytes of the signed JSON or the signature verification
   2014         // will fail.
   2015         assert_eq!(orig_binary, restored_claim.original_bytes.unwrap());
   2016 
   2017         // JSON examples
   2018         let json_str = claim
   2019             .to_json(AssertionStoreJsonFormat::OrderedList, true)
   2020             .expect("could not generate json");
   2021 
   2022         println!("Claim: {json_str}");
   2023     }
   2024 
   2025     #[test]
   2026     fn test_build_claim_generator_hints() {
   2027         // Create a new claim.
   2028         let mut claim = create_test_claim().expect("create test claim");
   2029 
   2030         claim.add_claim_generator_hint(
   2031             GH_FULL_VERSION_LIST,
   2032             Value::String(r#""user app";v="2.3.4""#.to_string()),
   2033         );
   2034         claim.add_claim_generator_hint(
   2035             GH_FULL_VERSION_LIST,
   2036             Value::String(r#""some toolkit";v="1.0.0""#.to_string()),
   2037         );
   2038 
   2039         let expected_value = r#""user app";v="2.3.4", "some toolkit";v="1.0.0""#;
   2040 
   2041         let cg_map = claim.get_claim_generator_hint_map().unwrap();
   2042         let value = &cg_map[GH_FULL_VERSION_LIST];
   2043 
   2044         assert_eq!(expected_value, value.as_str().unwrap());
   2045     }
   2046 
   2047     #[test]
   2048     fn test_build_claim_generator_info() {
   2049         // Create a new claim.
   2050         let mut claim = create_test_claim().expect("create test claim");
   2051 
   2052         let mut info = ClaimGeneratorInfo::new("test app");
   2053         info.version = Some("2.3.4".to_string());
   2054         info.icon = Some(UriOrResource::HashedUri(HashedUri::new(
   2055             "self#jumbf=c2pa.databoxes.data_box".to_string(),
   2056             None,
   2057             b"hashed",
   2058         )));
   2059         info.insert("something", "else");
   2060 
   2061         claim.add_claim_generator_info(info);
   2062 
   2063         let cgi = claim.claim_generator_info().unwrap();
   2064 
   2065         assert_eq!(&cgi[0].name, "test app");
   2066         assert_eq!(cgi[0].version.as_deref(), Some("2.3.4"));
   2067         if let UriOrResource::HashedUri(r) = cgi[0].icon.as_ref().unwrap() {
   2068             assert_eq!(r.hash(), b"hashed");
   2069         }
   2070     }
   2071 }