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

commit 68d0100946265da7e7cf82a348e9bdfc5d908d06
parent 60112c212e8f4bcd5fc715fb469bb32921f8e216
Author: mauricefisher64 <92736594+mauricefisher64@users.noreply.github.com>
Date:   Wed, 24 Aug 2022 10:51:43 -0400

Make sure there is  a single manifest store in the asset (#114)

* Check for more than one manifest store

* Fix grabbing wrong UUID

* Updated to fix playback issue

* Add comment
Diffstat:
Msdk/src/asset_handlers/bmff_io.rs | 149+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msdk/src/asset_handlers/jpeg_io.rs | 11++++++++++-
Msdk/src/asset_handlers/png_io.rs | 10++++++++++
Msdk/src/error.rs | 3+++
Msdk/tests/fixtures/video1.mp4 | 0
5 files changed, 108 insertions(+), 65 deletions(-)

diff --git a/sdk/src/asset_handlers/bmff_io.rs b/sdk/src/asset_handlers/bmff_io.rs @@ -769,6 +769,28 @@ pub(crate) fn build_bmff_tree( Ok(()) } +fn get_manifest_token( + bmff_tree: &Arena<BoxInfo>, + bmff_map: &HashMap<String, Vec<Token>>, +) -> Option<Token> { + if let Some(uuid_list) = bmff_map.get("/uuid") { + for uuid_token in uuid_list { + let box_info = &bmff_tree[*uuid_token]; + + // make sure it is UUID box + if box_info.data.box_type == BoxType::UuidBox { + if let Some(uuid) = &box_info.data.user_type { + // make sure it is a C2PA ContentProvenanceBox box + if vec_compare(&C2PA_UUID, uuid) { + return Some(*uuid_token); + } + } + } + } + } + None +} + impl CAILoader for BmffIO { fn read_cai(&self, reader: &mut dyn CAIRead) -> Result<Vec<u8>> { let start = reader.seek(SeekFrom::Current(0))?; @@ -793,71 +815,81 @@ impl CAILoader for BmffIO { // build layout of the BMFF structure build_bmff_tree(reader, size, &mut bmff_tree, &root_token, &mut bmff_map)?; + let mut output: Option<Vec<u8>> = None; + // grab top level (for now) C2PA box if let Some(uuid_list) = bmff_map.get("/uuid") { - let uuid_token = &uuid_list[0]; - let box_info = &bmff_tree[*uuid_token]; + let mut manifest_store_cnt = 0; - // make sure it is UUID box - if box_info.data.box_type == BoxType::UuidBox { - if let Some(uuid) = &box_info.data.user_type { - // make sure it is a C2PA ContentProvenanceBox box - if vec_compare(&C2PA_UUID, uuid) { - let mut data_len = box_info.data.size - HEADER_SIZE - 16 /*UUID*/; - - // set reader to start of box contents - skip_bytes_to(reader, box_info.data.offset + HEADER_SIZE + 16)?; - - // Fullbox => 8 bits for version 24 bits for flags - let (_version, _flags) = read_box_header_ext(reader)?; - data_len -= 4; - - // get the purpose - let mut purpose = Vec::with_capacity(64); - loop { - let mut buf = [0; 1]; - reader.read_exact(&mut buf)?; - data_len -= 1; - if buf[0] == 0x00 { - break; - } else { - purpose.push(buf[0]); - } - } + for uuid_token in uuid_list { + let box_info = &bmff_tree[*uuid_token]; - // is the purpose manifest? - if vec_compare(&purpose, MANIFEST.as_bytes()) { - // offset to first aux uuid with purpose merkle - let mut buf = [0u8; 8]; - reader.read_exact(&mut buf)?; - data_len -= 8; + // make sure it is UUID box + if box_info.data.box_type == BoxType::UuidBox { + if let Some(uuid) = &box_info.data.user_type { + // make sure it is a C2PA ContentProvenanceBox box + if vec_compare(&C2PA_UUID, uuid) { + let mut data_len = box_info.data.size - HEADER_SIZE - 16 /*UUID*/; - // offset to first aux uuid - let offset = u64::from_be_bytes(buf); + // set reader to start of box contents + skip_bytes_to(reader, box_info.data.offset + HEADER_SIZE + 16)?; - // if no offset this contains the manifest - if offset == 0 { - let mut buf = vec![0u8; data_len as usize]; - reader.read_exact(&mut buf)?; + // Fullbox => 8 bits for version 24 bits for flags + let (_version, _flags) = read_box_header_ext(reader)?; + data_len -= 4; - return Ok(buf); - } else { - // handle aux uuids - let mut buf = vec![0u8; data_len as usize]; + // get the purpose + let mut purpose = Vec::with_capacity(64); + loop { + let mut buf = [0; 1]; reader.read_exact(&mut buf)?; + data_len -= 1; + if buf[0] == 0x00 { + break; + } else { + purpose.push(buf[0]); + } + } - let _mm: BmffMerkleMap = serde_cbor::from_slice(&buf)?; + // is the purpose manifest? + if vec_compare(&purpose, MANIFEST.as_bytes()) { + // offset to first aux uuid with purpose merkle + let mut buf = [0u8; 8]; + reader.read_exact(&mut buf)?; + data_len -= 8; + + // offset to first aux uuid + let offset = u64::from_be_bytes(buf); + + // if no offset this contains the manifest + if offset == 0 { + if manifest_store_cnt == 0 { + let mut manifest = vec![0u8; data_len as usize]; + reader.read_exact(&mut manifest)?; + output = Some(manifest); + + manifest_store_cnt += 1; + } else { + return Err(Error::TooManyManifestStores); + } + } else { + // handle aux uuids + let mut buf = vec![0u8; data_len as usize]; + reader.read_exact(&mut buf)?; + + let _mm: BmffMerkleMap = serde_cbor::from_slice(&buf)?; + } + } else if vec_compare(&purpose, MERKLE.as_bytes()) { + // handle merkle boxes not yet handled + return Err(Error::UnsupportedType); } - } else if vec_compare(&purpose, MERKLE.as_bytes()) { - // handle merkle boxes not yet handled - return Err(Error::UnsupportedType); } } } } } - Err(Error::JumbfNotFound) + output.ok_or(Error::JumbfNotFound) } // Get XMP block @@ -912,25 +944,14 @@ impl AssetIO for BmffIO { let ftyp_size = ftyp_info.size; // get position to insert c2pa - let (c2pa_start, c2pa_length) = if let Some(uuid_tokens) = bmff_map.get("/uuid") { - let uuid_info = &bmff_tree[uuid_tokens[0]].data; - - // is this a C2PA manifest - let is_c2pa = if let Some(uuid) = &uuid_info.user_type { - // make sure it is a C2PA box - vec_compare(&C2PA_UUID, uuid) - } else { - false - }; + let (c2pa_start, c2pa_length) = + if let Some(c2pa_token) = get_manifest_token(&bmff_tree, &bmff_map) { + let uuid_info = &bmff_tree[c2pa_token].data; - if is_c2pa { (uuid_info.offset, Some(uuid_info.size)) } else { ((ftyp_offset + ftyp_size), None) - } - } else { - ((ftyp_offset + ftyp_size), None) - }; + }; let mut new_c2pa_box: Vec<u8> = Vec::with_capacity(store_bytes.len() * 2); let merkle_data: &[u8] = &[]; // not yet supported diff --git a/sdk/src/asset_handlers/jpeg_io.rs b/sdk/src/asset_handlers/jpeg_io.rs @@ -147,6 +147,8 @@ impl CAILoader for JpegIO { fn read_cai(&self, asset_reader: &mut dyn CAIRead) -> Result<Vec<u8>> { let mut buffer: Vec<u8> = Vec::new(); + let mut manifest_store_cnt = 0; + // load the bytes let mut buf: Vec<u8> = Vec::new(); asset_reader.read_to_end(&mut buf).map_err(Error::IoError)?; @@ -184,14 +186,21 @@ impl CAILoader for JpegIO { buffer.append(&mut raw_vec.as_mut_slice()[16..].to_vec()); cai_seg_cnt += 1; - } else { + } else if raw_vec.len() > 28 { + // must be at least 28 bytes for this to be a valid JUMBF box // check if this is a CAI JUMBF block let jumb_type = raw_vec.as_mut_slice()[24..28].to_vec(); let is_cai = vec_compare(&C2PA_MARKER, &jumb_type); if is_cai { + if manifest_store_cnt == 1 { + return Err(Error::TooManyManifestStores); + } + buffer.append(&mut raw_vec.as_mut_slice()[8..].to_vec()); cai_seg_cnt = 1; cai_en = en.clone(); // store the identifier + + manifest_store_cnt += 1; } } } diff --git a/sdk/src/asset_handlers/png_io.rs b/sdk/src/asset_handlers/png_io.rs @@ -110,6 +110,16 @@ fn get_png_chunk_positions(f: &mut dyn CAIRead) -> Result<Vec<PngChunkPos>> { fn get_cai_data(f: &mut dyn CAIRead) -> Result<Vec<u8>> { let ps = get_png_chunk_positions(f)?; + if ps + .clone() + .into_iter() + .filter(|pcp| pcp.name == CAI_CHUNK) + .count() + > 1 + { + return Err(Error::TooManyManifestStores); + } + let pcp = ps .into_iter() .find(|pcp| pcp.name == CAI_CHUNK) diff --git a/sdk/src/error.rs b/sdk/src/error.rs @@ -81,6 +81,9 @@ pub enum Error { #[error("update manifest is invalid")] UpdateManifestInvalid, + #[error("more than one manifest store detected")] + TooManyManifestStores, + /// The COSE Sign1 structure can not be parsed. #[error("COSE Sign1 structure can not be parsed: {coset_error}")] InvalidCoseSignature { diff --git a/sdk/tests/fixtures/video1.mp4 b/sdk/tests/fixtures/video1.mp4 Binary files differ.