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

data_hash.rs (11688B)


      1 // Copyright 2022 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 use std::{
     15     io::{Read, Seek},
     16     path::*,
     17 };
     18 
     19 use serde::{Deserialize, Serialize};
     20 use serde_bytes::ByteBuf;
     21 
     22 use crate::{
     23     assertion::{Assertion, AssertionBase, AssertionCbor},
     24     assertions::labels,
     25     asset_io::CAIRead,
     26     cbor_types::UriT,
     27     error::{Error, Result},
     28     utils::hash_utils::{
     29         hash_stream_by_alg, verify_asset_by_alg, verify_by_alg, verify_stream_by_alg, HashRange,
     30     },
     31 };
     32 
     33 const ASSERTION_CREATION_VERSION: usize = 1;
     34 
     35 /// Helper class to create DataHash assertion
     36 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
     37 pub struct DataHash {
     38     #[serde(skip_serializing_if = "Option::is_none")]
     39     pub exclusions: Option<Vec<HashRange>>,
     40 
     41     #[serde(skip_serializing_if = "Option::is_none")]
     42     pub name: Option<String>,
     43 
     44     #[serde(skip_serializing_if = "Option::is_none")]
     45     pub alg: Option<String>,
     46 
     47     #[serde(with = "serde_bytes")]
     48     pub hash: Vec<u8>,
     49     #[serde(with = "serde_bytes")]
     50     pub pad: Vec<u8>,
     51 
     52     // must use explicit ByteBuf here because  #[serde(with = "serde_bytes")] does not working if Option<Vec<u8>>
     53     #[serde(skip_serializing_if = "Option::is_none")]
     54     pub pad2: Option<serde_bytes::ByteBuf>,
     55 
     56     #[serde(skip_serializing_if = "Option::is_none")]
     57     pub url: Option<UriT>,
     58 
     59     #[serde(skip_deserializing, skip_serializing)]
     60     pub path: PathBuf,
     61 }
     62 
     63 impl DataHash {
     64     pub const LABEL: &'static str = labels::DATA_HASH;
     65 
     66     /// Create new DataHash instance
     67     pub fn new(name: &str, alg: &str) -> Self {
     68         DataHash {
     69             exclusions: None,
     70             name: Some(name.to_string()),
     71             alg: Some(alg.to_string()),
     72             hash: Vec::new(),
     73             pad: Vec::new(),
     74             pad2: None,
     75             url: None, //deprecated
     76             path: PathBuf::new(),
     77         }
     78     }
     79 
     80     pub fn add_exclusion(&mut self, exclusion: HashRange) {
     81         if self.exclusions.is_none() {
     82             self.exclusions = Some(Vec::new());
     83         }
     84 
     85         if let Some(ref mut e) = self.exclusions {
     86             e.push(exclusion);
     87         }
     88     }
     89 
     90     pub fn set_hash(&mut self, hash: Vec<u8>) {
     91         self.hash = hash;
     92     }
     93 
     94     pub fn add_padding(&mut self, padding: Vec<u8>) {
     95         self.pad = padding;
     96     }
     97 
     98     /// Checks if this is a remote hash
     99     pub const fn is_remote_hash(&self) -> bool {
    100         self.url.is_some()
    101     }
    102 
    103     /// generate the hash value for the Asset using the range from the DataHash
    104     pub fn gen_hash(&mut self, asset_path: &Path) -> Result<()> {
    105         self.hash = self.hash_from_asset(asset_path)?;
    106         self.path = PathBuf::from(asset_path);
    107         Ok(())
    108     }
    109 
    110     /// generate the hash value for the Asset stream using the range from the DataHash
    111     pub fn gen_hash_from_stream<R>(&mut self, stream: &mut R) -> Result<()>
    112     where
    113         R: Read + Seek + ?Sized,
    114     {
    115         self.hash = self.hash_from_stream(stream)?;
    116         Ok(())
    117     }
    118 
    119     // add padding to match size
    120     pub fn pad_to_size(&mut self, desired_size: usize) -> Result<()> {
    121         let mut curr_size = self.to_assertion()?.data().len();
    122 
    123         // this should not happen
    124         if curr_size > desired_size {
    125             return Err(Error::JumbfCreationError);
    126         }
    127 
    128         let mut last_pad = 0;
    129         loop {
    130             if curr_size == desired_size {
    131                 break;
    132             }
    133 
    134             if desired_size > curr_size {
    135                 self.pad.push(0x0);
    136                 curr_size = self.to_assertion()?.data().len();
    137                 last_pad += 1;
    138             } else {
    139                 match &self.pad2 {
    140                     Some(_pad2) => return Err(Error::JumbfCreationError),
    141                     None => {
    142                         // if we reach here we need a new second padding object to hit exact size
    143                         self.pad.clear();
    144                         let pad2_size = last_pad / 2; // spit across two pads
    145                         self.pad2 = Some(ByteBuf::from(vec![0u8; pad2_size]));
    146                         return self.pad_to_size(desired_size);
    147                     }
    148                 }
    149             }
    150         }
    151 
    152         Ok(())
    153     }
    154 
    155     /// generate the asset hash from a file asset using the constructed
    156     /// start and length values
    157     fn hash_from_asset(&mut self, asset_path: &Path) -> Result<Vec<u8>> {
    158         let mut file = std::fs::File::open(asset_path)?;
    159         self.hash_from_stream(&mut file)
    160     }
    161 
    162     /// generate the asset hash from a stream using the constructed
    163     /// start and length values
    164     pub fn hash_from_stream<R>(&mut self, stream: &mut R) -> Result<Vec<u8>>
    165     where
    166         R: Read + Seek + ?Sized,
    167     {
    168         if self.is_remote_hash() {
    169             return Err(Error::BadParam(
    170                 "asset hash is remote, not yet supported".to_owned(),
    171             ));
    172         }
    173 
    174         let alg = match self.alg {
    175             Some(ref a) => a.clone(),
    176             None => "sha256".to_string(),
    177         };
    178 
    179         // sort the exclusions
    180         let hash = match self.exclusions {
    181             Some(ref e) => hash_stream_by_alg(&alg, stream, Some(e.clone()), true)?,
    182             None => hash_stream_by_alg(&alg, stream, None, true)?,
    183         };
    184 
    185         if hash.is_empty() {
    186             Err(Error::BadParam("could not generate data hash".to_string()))
    187         } else {
    188             Ok(hash)
    189         }
    190     }
    191 
    192     // verify data using currently set algorithm or default alg is none currently set
    193     pub fn verify_in_memory_hash(&self, data: &[u8], alg: Option<&str>) -> Result<()> {
    194         if self.is_remote_hash() {
    195             return Err(Error::BadParam("asset hash is remote".to_owned()));
    196         }
    197 
    198         let curr_alg = match &self.alg {
    199             Some(a) => a.clone(),
    200             None => match alg {
    201                 Some(a) => a.to_owned(),
    202                 None => "sha256".to_string(),
    203             },
    204         };
    205 
    206         let exclusions = self.exclusions.as_ref().cloned();
    207 
    208         if verify_by_alg(&curr_alg, &self.hash, data, exclusions) {
    209             Ok(())
    210         } else {
    211             Err(Error::HashMismatch("Hashes do not match".to_owned()))
    212         }
    213     }
    214 
    215     ///  Used to verify a DataHash against an asset.
    216     #[allow(dead_code)] // used in tests
    217     pub fn verify_hash(&self, asset_path: &Path, alg: Option<&str>) -> Result<()> {
    218         if self.is_remote_hash() {
    219             return Err(Error::BadParam("asset hash is remote".to_owned()));
    220         }
    221 
    222         let curr_alg = alg.unwrap_or("sha256");
    223 
    224         let exclusions = self.exclusions.as_ref().cloned();
    225 
    226         if verify_asset_by_alg(curr_alg, &self.hash, asset_path, exclusions) {
    227             Ok(())
    228         } else {
    229             Err(Error::HashMismatch("Hashes do not match".to_owned()))
    230         }
    231     }
    232 
    233     // verify data using currently set algorithm or default alg is none currently set
    234     pub fn verify_stream_hash(&self, reader: &mut dyn CAIRead, alg: Option<&str>) -> Result<()> {
    235         if self.is_remote_hash() {
    236             return Err(Error::BadParam("asset hash is remote".to_owned()));
    237         }
    238 
    239         let curr_alg = match &self.alg {
    240             Some(a) => a.clone(),
    241             None => match alg {
    242                 Some(a) => a.to_owned(),
    243                 None => "sha256".to_string(),
    244             },
    245         };
    246 
    247         let exclusions = self.exclusions.as_ref().cloned();
    248 
    249         if verify_stream_by_alg(&curr_alg, &self.hash, reader, exclusions, true) {
    250             Ok(())
    251         } else {
    252             Err(Error::HashMismatch("Hashes do not match".to_owned()))
    253         }
    254     }
    255 
    256     /// Create a new instance from Assertion
    257     pub fn from_assertion(assertion: &Assertion) -> Result<Self> {
    258         assertion.check_version_from_label(ASSERTION_CREATION_VERSION)?;
    259         Self::from_cbor_assertion(assertion)
    260     }
    261 }
    262 
    263 impl AssertionCbor for DataHash {}
    264 
    265 impl AssertionBase for DataHash {
    266     const LABEL: &'static str = Self::LABEL;
    267     const VERSION: Option<usize> = Some(ASSERTION_CREATION_VERSION);
    268 
    269     fn to_assertion(&self) -> Result<Assertion> {
    270         if self.hash.is_empty() {
    271             return Err(Error::BadParam(
    272                 "no hash found, gen_hash must be called".to_string(),
    273             ));
    274         }
    275         Self::to_cbor_assertion(self)
    276     }
    277 
    278     fn from_assertion(assertion: &Assertion) -> Result<Self> {
    279         Self::from_cbor_assertion(assertion)
    280     }
    281 }
    282 
    283 #[cfg(test)]
    284 pub mod tests {
    285     #![allow(clippy::panic)]
    286     #![allow(clippy::unwrap_used)]
    287 
    288     use super::*;
    289     use crate::{assertion::AssertionData, utils::test::fixture_path};
    290 
    291     #[test]
    292     fn test_build_assertion() {
    293         // try json based assertion
    294         let mut data_hash = DataHash::new("Some data", "sha256");
    295         data_hash.add_exclusion(HashRange::new(0, 1234));
    296         data_hash.hash = vec![1, 2, 3];
    297 
    298         let assertion = data_hash.to_assertion().unwrap();
    299 
    300         println!("assertion label: {}", assertion.label());
    301 
    302         let j = assertion.data();
    303 
    304         let from_j = Assertion::from_data_cbor(&assertion.label(), j);
    305         let ad_ref = from_j.decode_data();
    306 
    307         let _assertion_type = match ad_ref {
    308             AssertionData::Cbor(ref _ad_cbor) => "cbor",
    309             AssertionData::Json(ref _ad_json) => "json",
    310             AssertionData::Binary(ref _ad_bin) => "binary",
    311             AssertionData::Uuid(_, _) => "uuid",
    312         };
    313 
    314         if let AssertionData::Cbor(ref ad_cbor) = ad_ref {
    315             // compare results
    316             let orig_d = assertion.decode_data();
    317             if let AssertionData::Cbor(ref orig_cbor) = orig_d {
    318                 // TO DISCUSS: Maurice, I'm not quite sure what we were testing
    319                 // in the original test. LMK if I've lost too much in translation
    320                 // here.
    321                 let orig_as_value: DataHash = serde_cbor::from_slice(orig_cbor).unwrap();
    322                 let ad_as_value: DataHash = serde_cbor::from_slice(ad_cbor).unwrap();
    323 
    324                 assert_eq!(orig_as_value, ad_as_value);
    325             } else {
    326                 panic!("Couldn't decode orig_d");
    327             }
    328         } else {
    329             panic!("Couldn't decode ad_ref");
    330         }
    331     }
    332 
    333     #[test]
    334     fn test_binary_round_trip() {
    335         let mut data_hash = DataHash::new("Some data", "sha256");
    336         data_hash.add_exclusion(HashRange::new(0x2000, 0x1000));
    337         data_hash.add_exclusion(HashRange::new(0x4000, 0x1000));
    338 
    339         // add some data to hash
    340         let ap = fixture_path("earth_apollo17.jpg");
    341 
    342         // generate the hash
    343         data_hash.gen_hash(&ap).unwrap();
    344 
    345         // verify
    346         data_hash.verify_hash(&ap, None).unwrap();
    347 
    348         let assertion = data_hash.to_assertion().unwrap();
    349 
    350         let orig_bytes = assertion.data();
    351 
    352         let assertion_from_binary = Assertion::from_data_cbor(&assertion.label(), orig_bytes);
    353 
    354         println!(
    355             "Label Match Test {} = {}",
    356             assertion.label(),
    357             assertion_from_binary.label()
    358         );
    359 
    360         assert_eq!(assertion.label(), assertion_from_binary.label());
    361 
    362         // compare the data as bytes
    363         assert_eq!(orig_bytes, assertion_from_binary.data());
    364         println!("Decoded binary matches");
    365     }
    366 }