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 }