bmff_hash.rs (32545B)
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 cmp, 16 collections::{hash_map::Entry::Vacant, HashMap}, 17 fmt, fs, 18 io::{BufReader, Cursor, SeekFrom}, 19 ops::Deref, 20 path::Path, 21 }; 22 23 use mp4::*; 24 use serde::{ 25 de::{SeqAccess, Visitor}, 26 ser::SerializeSeq, 27 Deserialize, Deserializer, Serialize, Serializer, 28 }; 29 use serde_bytes::ByteBuf; 30 use sha2::{Digest, Sha256, Sha384, Sha512}; 31 32 use crate::{ 33 assertion::{Assertion, AssertionBase, AssertionCbor}, 34 assertions::labels, 35 asset_handlers::bmff_io::{bmff_to_jumbf_exclusions, read_bmff_c2pa_boxes, BoxInfoLite}, 36 asset_io::CAIRead, 37 cbor_types::UriT, 38 utils::{ 39 hash_utils::{ 40 concat_and_hash, hash_stream_by_alg, vec_compare, verify_stream_by_alg, HashRange, 41 Hasher, 42 }, 43 merkle::C2PAMerkleTree, 44 }, 45 Error, 46 }; 47 48 const ASSERTION_CREATION_VERSION: usize = 2; 49 50 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 51 pub struct ExclusionsMap { 52 pub xpath: String, 53 pub length: Option<u32>, 54 pub data: Option<Vec<DataMap>>, 55 pub subset: Option<Vec<SubsetMap>>, 56 pub version: Option<u8>, 57 pub flags: Option<ByteBuf>, 58 pub exact: Option<bool>, 59 } 60 61 impl ExclusionsMap { 62 pub const fn new(xpath: String) -> Self { 63 ExclusionsMap { 64 xpath, 65 length: None, 66 data: None, 67 subset: None, 68 version: None, 69 flags: None, 70 exact: None, 71 } 72 } 73 } 74 75 #[derive(Clone, Debug, PartialEq, Eq)] 76 pub struct VecByteBuf(Vec<ByteBuf>); 77 78 impl Deref for VecByteBuf { 79 type Target = Vec<ByteBuf>; 80 81 fn deref(&self) -> &Vec<ByteBuf> { 82 &self.0 83 } 84 } 85 86 impl Serialize for VecByteBuf { 87 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> 88 where 89 S: Serializer, 90 { 91 let mut seq = serializer.serialize_seq(Some(self.0.len()))?; 92 for e in &self.0 { 93 seq.serialize_element(e)?; 94 } 95 seq.end() 96 } 97 } 98 99 struct VecByteBufVisitor; 100 101 impl<'de> Visitor<'de> for VecByteBufVisitor { 102 type Value = VecByteBuf; 103 104 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 105 formatter.write_str("Vec<ByteBuf>") 106 } 107 108 fn visit_seq<V>(self, mut visitor: V) -> std::result::Result<Self::Value, V::Error> 109 where 110 V: SeqAccess<'de>, 111 { 112 let len = cmp::min(visitor.size_hint().unwrap_or(0), 4096); 113 let mut byte_bufs: Vec<ByteBuf> = Vec::with_capacity(len); 114 115 while let Some(b) = visitor.next_element()? { 116 byte_bufs.push(b); 117 } 118 119 Ok(VecByteBuf(byte_bufs)) 120 } 121 } 122 123 impl<'de> Deserialize<'de> for VecByteBuf { 124 fn deserialize<D>(deserializer: D) -> std::result::Result<VecByteBuf, D::Error> 125 where 126 D: Deserializer<'de>, 127 { 128 deserializer.deserialize_seq(VecByteBufVisitor {}) 129 } 130 } 131 132 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 133 pub struct MerkleMap { 134 #[serde(rename = "uniqueId")] 135 pub unique_id: u32, 136 137 #[serde(rename = "localId")] 138 pub local_id: u32, 139 140 pub count: u32, 141 142 #[serde(skip_serializing_if = "Option::is_none")] 143 pub alg: Option<String>, 144 145 #[serde(rename = "initHash", skip_serializing_if = "Option::is_none")] 146 pub init_hash: Option<ByteBuf>, 147 148 pub hashes: VecByteBuf, 149 } 150 151 impl MerkleMap { 152 pub fn hash_check(&self, indx: u32, merkle_hash: &[u8]) -> bool { 153 if let Some(h) = self.hashes.get(indx as usize) { 154 vec_compare(h, merkle_hash) 155 } else { 156 false 157 } 158 } 159 160 pub fn check_merkle_tree( 161 &self, 162 alg: &str, 163 hash: &[u8], 164 location: u32, 165 proof: &Option<VecByteBuf>, 166 ) -> bool { 167 if location >= self.count { 168 return false; 169 } 170 171 let mut index = location; 172 let mut hash = hash.to_vec(); 173 let layers = C2PAMerkleTree::to_layout(self.count as usize); 174 175 if let Some(hashes) = proof { 176 // playback proof 177 let mut proof_index = 0; 178 for layer in layers { 179 let is_right = index % 2 == 1; 180 181 if layer == self.hashes.len() { 182 break; 183 } 184 185 if is_right { 186 if index - 1 < layer as u32 { 187 // make sure proof structure is valid 188 if let Some(proof_hash) = hashes.get(proof_index) { 189 hash = concat_and_hash(alg, proof_hash, Some(&hash)); 190 proof_index += 1; 191 } else { 192 return false; 193 } 194 } 195 } else if index + 1 < layer as u32 { 196 // make sure proof structure is valid 197 if let Some(proof_hash) = hashes.get(proof_index) { 198 hash = concat_and_hash(alg, &hash, Some(proof_hash)); 199 proof_index += 1; 200 } else { 201 return false; 202 } 203 } 204 205 index /= 2; 206 } 207 } else { 208 //empty proof playback 209 for layer in layers { 210 if layer == self.hashes.len() { 211 break; 212 } 213 index /= 2; 214 } 215 } 216 217 self.hash_check(index, &hash) 218 } 219 } 220 221 #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] 222 pub struct BmffMerkleMap { 223 #[serde(rename = "uniqueId")] 224 pub unique_id: u32, 225 226 #[serde(rename = "localId")] 227 pub local_id: u32, 228 229 pub location: u32, 230 231 pub hashes: Option<VecByteBuf>, 232 } 233 234 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 235 pub struct DataMap { 236 pub offset: u32, 237 #[serde(with = "serde_bytes")] 238 pub value: Vec<u8>, 239 } 240 241 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 242 pub struct SubsetMap { 243 pub offset: u32, 244 pub length: u32, 245 } 246 247 /// Helper class to create BmffHash assertion. (These are auto-generated by the SDK.) 248 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 249 pub struct BmffHash { 250 exclusions: Vec<ExclusionsMap>, 251 252 #[serde(skip_serializing_if = "Option::is_none")] 253 alg: Option<String>, 254 255 #[serde(skip_serializing_if = "Option::is_none")] 256 hash: Option<ByteBuf>, 257 258 #[serde(skip_serializing_if = "Option::is_none")] 259 merkle: Option<Vec<MerkleMap>>, 260 261 #[serde(skip_serializing_if = "Option::is_none")] 262 name: Option<String>, 263 264 #[serde(skip_serializing)] 265 url: Option<UriT>, // deprecated in V2 and not to be used 266 267 #[serde(skip)] 268 bmff_version: usize, 269 } 270 271 impl BmffHash { 272 pub const LABEL: &'static str = labels::BMFF_HASH; 273 274 pub fn new(name: &str, alg: &str, url: Option<UriT>) -> Self { 275 BmffHash { 276 exclusions: Vec::new(), 277 alg: Some(alg.to_string()), 278 hash: None, 279 merkle: None, 280 name: Some(name.to_string()), 281 url, 282 bmff_version: ASSERTION_CREATION_VERSION, 283 } 284 } 285 286 pub fn exclusions(&self) -> &[ExclusionsMap] { 287 self.exclusions.as_ref() 288 } 289 290 pub fn exclusions_mut(&mut self) -> &mut Vec<ExclusionsMap> { 291 &mut self.exclusions 292 } 293 294 pub const fn alg(&self) -> Option<&String> { 295 self.alg.as_ref() 296 } 297 298 pub fn hash(&self) -> Option<&Vec<u8>> { 299 self.hash.as_deref() 300 } 301 302 pub const fn merkle(&self) -> Option<&Vec<MerkleMap>> { 303 self.merkle.as_ref() 304 } 305 306 pub fn set_hash(&mut self, hash: Vec<u8>) { 307 self.hash = Some(ByteBuf::from(hash)); 308 } 309 310 pub const fn name(&self) -> Option<&String> { 311 self.name.as_ref() 312 } 313 314 pub const fn url(&self) -> Option<&UriT> { 315 self.url.as_ref() 316 } 317 318 pub const fn bmff_version(&self) -> usize { 319 self.bmff_version 320 } 321 322 fn set_bmff_version(&mut self, version: usize) { 323 self.bmff_version = version; 324 } 325 326 /// Returns `true` if this is a remote hash. 327 pub const fn is_remote_hash(&self) -> bool { 328 self.url.is_some() 329 } 330 331 pub fn set_merkle(&mut self, merkle: Vec<MerkleMap>) { 332 self.merkle = Some(merkle); 333 } 334 335 /// Generate the hash value for the asset using the range from the BmffHash. 336 pub fn gen_hash_from_stream(&mut self, stream: &mut dyn CAIRead) -> crate::error::Result<()> { 337 self.hash = Some(ByteBuf::from(self.hash_from_stream(stream)?)); 338 //self.path = PathBuf::from(asset_path); 339 Ok(()) 340 } 341 342 /// Generate the hash value for the asset using the range from the BmffHash. 343 #[cfg(feature = "file_io")] 344 pub fn gen_hash(&mut self, asset_path: &Path) -> crate::error::Result<()> { 345 let mut file = std::fs::File::open(asset_path)?; 346 self.hash = Some(ByteBuf::from(self.hash_from_stream(&mut file)?)); 347 //self.path = PathBuf::from(asset_path); 348 Ok(()) 349 } 350 351 /// Generate the asset hash from an asset stream using the constructed 352 /// start and length values. 353 fn hash_from_stream(&mut self, stream: &mut dyn CAIRead) -> crate::error::Result<Vec<u8>> { 354 if self.is_remote_hash() { 355 return Err(Error::BadParam( 356 "asset hash is remote, not yet supported".to_owned(), 357 )); 358 } 359 360 let alg = match self.alg { 361 Some(ref a) => a.clone(), 362 None => "sha256".to_string(), 363 }; 364 365 let bmff_exclusions = &self.exclusions; 366 367 // convert BMFF exclusion map to flat exclusion list 368 let exclusions = bmff_to_jumbf_exclusions(stream, bmff_exclusions, self.bmff_version > 1)?; 369 370 let hash = hash_stream_by_alg(&alg, stream, Some(exclusions), true)?; 371 372 if hash.is_empty() { 373 Err(Error::BadParam("could not generate data hash".to_string())) 374 } else { 375 Ok(hash) 376 } 377 } 378 379 pub fn verify_in_memory_hash( 380 &self, 381 data: &[u8], 382 alg: Option<&str>, 383 ) -> crate::error::Result<()> { 384 let mut reader = Cursor::new(data); 385 386 self.verify_stream_hash(&mut reader, alg) 387 } 388 389 // The BMFFMerklMaps are stored contiguous in the file. Break this Vec into groups based on 390 // the MerkleMap it matches. 391 fn split_bmff_merkle_map( 392 &self, 393 bmff_merkle_map: Vec<BmffMerkleMap>, 394 ) -> crate::Result<HashMap<u32, Vec<BmffMerkleMap>>> { 395 let mut current = bmff_merkle_map; 396 let mut output = HashMap::new(); 397 if let Some(mm) = self.merkle() { 398 for m in mm { 399 let rest = current.split_off(m.count as usize); 400 401 if current.len() == m.count as usize { 402 output.insert(m.local_id, current.to_owned()); 403 } else { 404 return Err(Error::HashMismatch("MerkleMap count incorrect".to_string())); 405 } 406 current = rest; 407 } 408 } else { 409 output.insert(0, current); 410 } 411 Ok(output) 412 } 413 414 // Breaks box runs at fragment boundaries (moof boxes) 415 fn split_fragment_boxes(boxes: &[BoxInfoLite]) -> Vec<Vec<BoxInfoLite>> { 416 let mut moof_list = Vec::new(); 417 418 // start from 1st moof 419 if let Some(pos) = boxes.iter().position(|b| b.path == "moof") { 420 let mut box_list = vec![boxes[pos].clone()]; 421 422 if pos == 0 { 423 return moof_list; // this does not contain fragmented content 424 } 425 426 for b in boxes[pos + 1..].iter() { 427 if b.path == "moof" { 428 moof_list.push(box_list); // save box list 429 box_list = Vec::new(); // start new box list 430 } 431 box_list.push(b.clone()); 432 } 433 moof_list.push(box_list); // save last list 434 } 435 moof_list 436 } 437 438 pub fn verify_hash(&self, asset_path: &Path, alg: Option<&str>) -> crate::error::Result<()> { 439 let mut data = fs::File::open(asset_path)?; 440 self.verify_stream_hash(&mut data, alg) 441 } 442 443 /* Verifies BMFF hashes from a single file asset. The following variants are handled 444 A single BMFF asset with only a file hash 445 A single BMMF asset with Merkle tree hash 446 Timed media (Merkle hashes over track chunks) 447 Untimed media (Merkle hashes over iloc locations) 448 A single BMFF asset containing all fragments (Merkle hashes over moof ranges). 449 */ 450 pub fn verify_stream_hash( 451 &self, 452 reader: &mut dyn CAIRead, 453 alg: Option<&str>, 454 ) -> crate::error::Result<()> { 455 if self.is_remote_hash() { 456 return Err(Error::BadParam( 457 "asset hash is remote, not yet supported".to_owned(), 458 )); 459 } 460 461 reader.rewind()?; 462 let size = stream_len(reader)?; 463 464 let curr_alg = match &self.alg { 465 Some(a) => a.clone(), 466 None => match alg { 467 Some(a) => a.to_owned(), 468 None => "sha256".to_string(), 469 }, 470 }; 471 472 // convert BMFF exclusion map to flat exclusion list 473 let exclusions = bmff_to_jumbf_exclusions(reader, &self.exclusions, self.bmff_version > 1)?; 474 475 // handle file level hashing 476 if let Some(hash) = self.hash() { 477 if !verify_stream_by_alg(&curr_alg, hash, reader, Some(exclusions.clone()), true) { 478 return Err(Error::HashMismatch( 479 "BMFF file level hash mismatch".to_string(), 480 )); 481 } 482 } 483 484 // merkle hashed BMFF 485 if let Some(mm_vec) = self.merkle() { 486 // get merkle boxes from asset 487 let c2pa_boxes = read_bmff_c2pa_boxes(reader)?; 488 let bmff_merkle = c2pa_boxes.bmff_merkle; 489 let box_infos = c2pa_boxes.box_infos; 490 491 let first_moof = box_infos.iter().find(|b| b.path == "moof"); 492 let is_fragmented = first_moof.is_some(); 493 494 // check initialization segments (must do here in separate loop since MP4 will consume the reader) 495 for mm in mm_vec { 496 let alg = match &mm.alg { 497 Some(a) => a, 498 None => self 499 .alg() 500 .ok_or(Error::HashMismatch("no algorithm found".to_string()))?, 501 }; 502 503 if let Some(init_hash) = &mm.init_hash { 504 if let Some(moof_box) = first_moof { 505 // add the moof to end exclusion 506 let moof_exclusion = HashRange::new( 507 moof_box.offset as usize, 508 (size - moof_box.offset) as usize, 509 ); 510 511 let mut mm_exclusions = exclusions.clone(); 512 mm_exclusions.push(moof_exclusion); 513 514 if !verify_stream_by_alg(alg, init_hash, reader, Some(mm_exclusions), true) 515 { 516 return Err(Error::HashMismatch( 517 "BMFF file level hash mismatch".to_string(), 518 )); 519 } 520 } else { 521 return Err(Error::HashMismatch( 522 "BMFF inithash must not be present for non-fragmented media".to_owned(), 523 )); 524 } 525 } 526 } 527 528 // is this a fragmented BMFF 529 if is_fragmented { 530 for mm in mm_vec { 531 let alg = match &mm.alg { 532 Some(a) => a, 533 None => self 534 .alg() 535 .ok_or(Error::HashMismatch("no algorithm found".to_string()))?, 536 }; 537 538 let moof_chunks = BmffHash::split_fragment_boxes(&box_infos); 539 540 // make sure there is a 1-1 mapping of moof chunks and Merkle values 541 if moof_chunks.len() != mm.count as usize 542 || bmff_merkle.len() != mm.count as usize 543 { 544 return Err(Error::HashMismatch( 545 "Incorrect number of fragments hashes".to_owned(), 546 )); 547 } 548 549 // build Merkle tree for the moof chucks minus the excluded ranges 550 for (index, boxes) in moof_chunks.iter().enumerate() { 551 // include just the range of this chunk so exclude boxes before and after 552 let mut curr_exclusions = exclusions.clone(); 553 554 // before box exclusion starts at beginning of file until the start of this chunk 555 let before_box_start = 0; 556 let before_box_len = match boxes.first() { 557 Some(first) => first.offset as usize, 558 None => 0, 559 }; 560 let before_box_exclusion = HashRange::new(before_box_start, before_box_len); 561 curr_exclusions.push(before_box_exclusion); 562 563 // after box exclusion continues to the end of the file 564 let after_box_start = match boxes.last() { 565 Some(last) => last.offset + last.size, 566 None => 0, 567 }; 568 let after_box_len = size - after_box_start; 569 let after_box_exclusion = 570 HashRange::new(after_box_start as usize, after_box_len as usize); 571 curr_exclusions.push(after_box_exclusion); 572 573 // hash the specified range 574 let hash = hash_stream_by_alg(alg, reader, Some(curr_exclusions), true)?; 575 576 let bmff_mm = &bmff_merkle[index]; 577 578 // check MerkleMap for the hash 579 if !mm.check_merkle_tree(alg, &hash, bmff_mm.location, &bmff_mm.hashes) { 580 return Err(Error::HashMismatch("Fragment not valid".to_string())); 581 } 582 } 583 } 584 return Ok(()); 585 } else if box_infos.iter().any(|b| b.path == "moov") { 586 // timed media case 587 588 let track_to_bmff_merkle_map = if bmff_merkle.is_empty() { 589 HashMap::new() 590 } else { 591 self.split_bmff_merkle_map(bmff_merkle)? 592 }; 593 594 reader.rewind()?; 595 let buf_reader = BufReader::new(reader); 596 let mut mp4 = mp4::Mp4Reader::read_header(buf_reader, size) 597 .map_err(|_e| Error::InvalidAsset("Could not parse BMFF".to_string()))?; 598 let track_count = mp4.tracks().len(); 599 600 for mm in mm_vec { 601 let alg = match &mm.alg { 602 Some(a) => a, 603 None => self 604 .alg() 605 .ok_or(Error::HashMismatch("no algorithm found".to_string()))?, 606 }; 607 608 if track_count > 0 { 609 // timed media case 610 let track = { 611 // clone so we can borrow later 612 let tt = mp4.tracks().get(&mm.local_id).ok_or(Error::HashMismatch( 613 "Merkle location not found".to_owned(), 614 ))?; 615 616 Mp4Track { 617 trak: tt.trak.clone(), 618 trafs: tt.trafs.clone(), 619 default_sample_duration: tt.default_sample_duration, 620 } 621 }; 622 623 let sample_cnt = track.sample_count(); 624 if sample_cnt == 0 { 625 return Err(Error::InvalidAsset("No samples".to_string())); 626 } 627 628 let track_id = track.track_id(); 629 630 // create sample to chunk mapping 631 // create the Merkle tree per samples in a chunk 632 let mut chunk_hash_map: HashMap<u32, Hasher> = HashMap::new(); 633 let stsc = &track.trak.mdia.minf.stbl.stsc; 634 for sample_id in 1..=sample_cnt { 635 let stsc_idx = stsc_index(&track, sample_id)?; 636 637 let stsc_entry = &stsc.entries[stsc_idx]; 638 639 let first_chunk = stsc_entry.first_chunk; 640 let first_sample = stsc_entry.first_sample; 641 let samples_per_chunk = stsc_entry.samples_per_chunk; 642 643 let chunk_id = 644 first_chunk + (sample_id - first_sample) / samples_per_chunk; 645 646 // add chunk Hasher if needed 647 if let Vacant(e) = chunk_hash_map.entry(chunk_id) { 648 // get hasher for algorithm 649 let hasher_enum = match alg.as_str() { 650 "sha256" => Hasher::SHA256(Sha256::new()), 651 "sha384" => Hasher::SHA384(Sha384::new()), 652 "sha512" => Hasher::SHA512(Sha512::new()), 653 _ => { 654 return Err(Error::HashMismatch( 655 "no algorithm found".to_string(), 656 )) 657 } 658 }; 659 660 e.insert(hasher_enum); 661 } 662 663 if let Ok(Some(sample)) = &mp4.read_sample(track_id, sample_id) { 664 let h = chunk_hash_map.get_mut(&chunk_id).ok_or( 665 Error::HashMismatch( 666 "Bad Merkle tree sample mapping".to_string(), 667 ), 668 )?; 669 // add sample data to hash 670 h.update(&sample.bytes); 671 } else { 672 return Err(Error::HashMismatch( 673 "Merle location not found".to_owned(), 674 )); 675 } 676 } 677 678 // finalize leaf hashes 679 let mut leaf_hashes = Vec::new(); 680 for chunk_bmff_mm in &track_to_bmff_merkle_map[&track_id] { 681 match chunk_hash_map.remove(&(chunk_bmff_mm.location + 1)) { 682 Some(h) => { 683 let h = Hasher::finalize(h); 684 leaf_hashes.push(h); 685 } 686 None => { 687 return Err(Error::HashMismatch( 688 "Could not generate hash".to_owned(), 689 )) 690 } 691 } 692 } 693 694 for chunk_bmff_mm in &track_to_bmff_merkle_map[&track_id] { 695 let hash = &leaf_hashes[chunk_bmff_mm.location as usize]; 696 697 // check MerkleMap for the hash 698 if !mm.check_merkle_tree( 699 alg, 700 hash, 701 chunk_bmff_mm.location, 702 &chunk_bmff_mm.hashes, 703 ) { 704 return Err(Error::HashMismatch("Fragment not valid".to_string())); 705 } 706 } 707 } 708 } 709 } else { 710 // non-timed media so use iloc (awaiting use case/example since the iloc varies by format) 711 return Err(Error::HashMismatch( 712 "Merkle iloc not yet supported".to_owned(), 713 )); 714 } 715 } 716 717 Ok(()) 718 } 719 720 // Used to verify fragmented BMFF assets spread across multiple file. 721 pub fn verify_stream_segment( 722 &self, 723 init_stream: &mut dyn CAIRead, 724 fragment_stream: &mut dyn CAIRead, 725 alg: Option<&str>, 726 ) -> crate::Result<()> { 727 let curr_alg = match &self.alg { 728 Some(a) => a.clone(), 729 None => match alg { 730 Some(a) => a.to_owned(), 731 None => "sha256".to_string(), 732 }, 733 }; 734 735 // handle file level hashing 736 if self.hash().is_some() { 737 return Err(Error::HashMismatch( 738 "Hash value should not be present for a fragmented BMFF asset".to_string(), 739 )); 740 } 741 742 // Merkle hashed BMFF 743 if let Some(mm_vec) = self.merkle() { 744 // get merkle boxes from segment 745 let c2pa_boxes = read_bmff_c2pa_boxes(fragment_stream)?; 746 let bmff_merkle = c2pa_boxes.bmff_merkle; 747 748 if bmff_merkle.is_empty() { 749 return Err(Error::HashMismatch("Fragment had no MerkleMap".to_string())); 750 } 751 752 for bmff_mm in bmff_merkle { 753 // find matching MerkleMap for this uniqueId & localId 754 if let Some(mm) = mm_vec 755 .iter() 756 .find(|mm| mm.unique_id == bmff_mm.unique_id && mm.local_id == bmff_mm.local_id) 757 { 758 let alg = match &mm.alg { 759 Some(a) => a, 760 None => &curr_alg, 761 }; 762 763 // check the inithash (for fragmented MP4 with multiple files this is the hash of the init_segment minus any exclusions) 764 if let Some(init_hash) = &mm.init_hash { 765 let bmff_exclusions = &self.exclusions; 766 767 // convert BMFF exclusion map to flat exclusion list 768 init_stream.rewind()?; 769 let exclusions = bmff_to_jumbf_exclusions( 770 init_stream, 771 bmff_exclusions, 772 self.bmff_version > 1, 773 )?; 774 775 if !verify_stream_by_alg( 776 alg, 777 init_hash, 778 init_stream, 779 Some(exclusions), 780 true, 781 ) { 782 return Err(Error::HashMismatch("BMFF inithash mismatch".to_string())); 783 } 784 785 let fragment_exclusions = bmff_to_jumbf_exclusions( 786 fragment_stream, 787 bmff_exclusions, 788 self.bmff_version > 1, 789 )?; 790 791 // hash the entire fragment minus exclusions 792 let hash = hash_stream_by_alg( 793 alg, 794 fragment_stream, 795 Some(fragment_exclusions), 796 true, 797 )?; 798 799 // check MerkleMap for the hash 800 if !mm.check_merkle_tree(alg, &hash, bmff_mm.location, &bmff_mm.hashes) { 801 return Err(Error::HashMismatch("Fragment not valid".to_string())); 802 } 803 } 804 } else { 805 return Err(Error::HashMismatch("Fragment had no MerkleMap".to_string())); 806 } 807 } 808 } else { 809 return Err(Error::HashMismatch( 810 "Merkle value must be present for a fragmented BMFF asset".to_string(), 811 )); 812 } 813 814 Ok(()) 815 } 816 } 817 818 impl AssertionCbor for BmffHash {} 819 820 impl AssertionBase for BmffHash { 821 const LABEL: &'static str = Self::LABEL; 822 const VERSION: Option<usize> = Some(ASSERTION_CREATION_VERSION); 823 824 // todo: this mechanism needs to change since a struct could support different versions 825 826 fn to_assertion(&self) -> crate::error::Result<Assertion> { 827 Self::to_cbor_assertion(self) 828 } 829 830 fn from_assertion(assertion: &Assertion) -> crate::error::Result<Self> { 831 let mut bmff_hash = Self::from_cbor_assertion(assertion)?; 832 bmff_hash.set_bmff_version(assertion.get_ver().unwrap_or(1)); 833 834 Ok(bmff_hash) 835 } 836 } 837 838 fn stsc_index(track: &Mp4Track, sample_id: u32) -> crate::Result<usize> { 839 if track.trak.mdia.minf.stbl.stsc.entries.is_empty() { 840 return Err(Error::InvalidAsset("BMFF has no stsc entries".to_string())); 841 } 842 for (i, entry) in track.trak.mdia.minf.stbl.stsc.entries.iter().enumerate() { 843 if sample_id < entry.first_sample { 844 return if i == 0 { 845 Err(Error::InvalidAsset("BMFF no sample not found".to_string())) 846 } else { 847 Ok(i - 1) 848 }; 849 } 850 } 851 Ok(track.trak.mdia.minf.stbl.stsc.entries.len() - 1) 852 } 853 854 fn stream_len(reader: &mut dyn CAIRead) -> crate::Result<u64> { 855 let old_pos = reader.stream_position()?; 856 let len = reader.seek(SeekFrom::End(0))?; 857 858 if old_pos != len { 859 reader.seek(SeekFrom::Start(old_pos))?; 860 } 861 862 Ok(len) 863 } 864 865 /* we need shippable examples 866 #[cfg(test)] 867 pub mod tests { 868 #![allow(clippy::expect_used)] 869 #![allow(clippy::panic)] 870 #![allow(clippy::unwrap_used)] 871 872 //use tempfile::tempdir; 873 874 //use super::*; 875 use crate::utils::test::fixture_path; 876 877 #[cfg(not(target_arch = "wasm32"))] 878 #[test] 879 fn test_fragemented_mp4() { 880 use crate::{ 881 assertions::BmffHash, asset_handlers::bmff_io::BmffIO, asset_io::AssetIO, 882 status_tracker::DetailedStatusTracker, store::Store, AssertionBase, 883 }; 884 885 let init_stream_path = fixture_path("dashinit.mp4"); 886 let segment_stream_path = fixture_path("dash1.m4s"); 887 let segment_stream_path10 = fixture_path("dash10.m4s"); 888 let segment_stream_path11 = fixture_path("dash11.m4s"); 889 890 891 let mut init_stream = std::fs::File::open(init_stream_path).unwrap(); 892 let mut segment_stream = std::fs::File::open(segment_stream_path).unwrap(); 893 let mut segment_stream10 = std::fs::File::open(segment_stream_path10).unwrap(); 894 let mut segment_stream11 = std::fs::File::open(segment_stream_path11).unwrap(); 895 896 897 let mut log = DetailedStatusTracker::default(); 898 899 let bmff_io = BmffIO::new("mp4"); 900 let bmff_handler = bmff_io.get_reader(); 901 902 let manifest_bytes = bmff_handler.read_cai(&mut init_stream).unwrap(); 903 let store = Store::from_jumbf(&manifest_bytes, &mut log).unwrap(); 904 905 // get the bmff hashes 906 let claim = store.provenance_claim().unwrap(); 907 for dh_assertion in claim.hash_assertions() { 908 if dh_assertion.label_root() == BmffHash::LABEL { 909 let bmff_hash = BmffHash::from_assertion(dh_assertion).unwrap(); 910 911 bmff_hash 912 .verify_stream_segment(&mut init_stream, &mut segment_stream, None) 913 .unwrap(); 914 915 bmff_hash 916 .verify_stream_segment(&mut init_stream, &mut segment_stream10, None) 917 .unwrap(); 918 919 bmff_hash 920 .verify_stream_segment(&mut init_stream, &mut segment_stream11, None) 921 .unwrap(); 922 } 923 } 924 } 925 } 926 */