diff --git a/Cargo.lock b/Cargo.lock index eed623e77..183366d79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -534,6 +534,7 @@ version = "0.75.19" dependencies = [ "c2pa", "cbindgen", + "digest", "scopeguard", "serde", "serde_json", diff --git a/c2pa_c_ffi/Cargo.toml b/c2pa_c_ffi/Cargo.toml index 59acfd956..974f71269 100644 --- a/c2pa_c_ffi/Cargo.toml +++ b/c2pa_c_ffi/Cargo.toml @@ -34,6 +34,7 @@ c2pa = { path = "../sdk", version = "0.75.19", default-features = false, feature "http_reqwest_blocking", "http_reqwest", ] } +digest = "0.10.7" scopeguard = "1.2.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/c2pa_c_ffi/src/c_api.rs b/c2pa_c_ffi/src/c_api.rs index 73a83218d..17f2e4f09 100644 --- a/c2pa_c_ffi/src/c_api.rs +++ b/c2pa_c_ffi/src/c_api.rs @@ -20,8 +20,13 @@ use std::{ #[cfg(feature = "file_io")] use c2pa::Ingredient; use c2pa::{ - assertions::DataHash, identity::validator::CawgValidator, Builder as C2paBuilder, - CallbackSigner, Context, Reader as C2paReader, Settings as C2paSettings, SigningAlg, + assertions::{ + labels::{parse_label, BMFF_HASH}, + BmffHash, DataHash, + }, + identity::validator::CawgValidator, + Builder as C2paBuilder, CallbackSigner, Context, Hasher as C2paHasher, Reader as C2paReader, + Settings as C2paSettings, SigningAlg, }; use tokio::runtime::Runtime; // cawg validator requires async @@ -88,6 +93,10 @@ mod cbindgen_fix { #[repr(C)] #[allow(dead_code)] pub struct C2paSettings; + + #[repr(C)] + #[allow(dead_code)] + pub struct C2paHasher; } type C2paContextBuilder = Context; @@ -870,16 +879,6 @@ pub unsafe extern "C" fn c2pa_reader_with_fragment( box_tracked!(result) } -/// Creates a new C2paReader from a shared Context. -/// -/// # Safety -/// -/// * `context` must be a valid pointer to a C2paContext object. -/// * The context pointer remains valid after this call and can be reused. -/// -/// # Returns -/// -/// A pointer to a newly allocated C2paReader, or NULL on error. /// Creates and verifies a C2paReader from a file path. /// This allows a client to use Rust's file I/O to read the file /// Parameters @@ -1567,6 +1566,302 @@ pub unsafe extern "C" fn c2pa_manifest_bytes_free(manifest_bytes_ptr: *const c_u cimpl_free!(manifest_bytes_ptr); } +/// Get a C2paHasher for the specified algorithm +/// +/// #Parameters +/// * alg: pointer to a C string specifying the supported algorithm {"sha256, "sha284", "sha512"}. +/// +/// # Errors +/// Returns NULL if there were errors, otherwise returns a pointer to a C2pHasher. +/// The error string can be retrieved by calling c2pa_error. +/// +/// # Safety +/// The returned value MUST be released by calling c2pa_reader_free or c2pa_hasher_finalize +/// and it is no longer valid after that call. +/// +/// # Example +/// ```c +/// auto result = c2pa_hasher_from_alg("sha256"); +/// if (result == NULL) { +/// let error = c2pa_error(); +/// printf("Error: %s\n", error); +/// c2pa_hasher_free(result); +/// } +/// ``` +#[no_mangle] +pub unsafe extern "C" fn c2pa_hasher_from_alg(alg: *const c_char) -> *mut C2paHasher { + let alg = cstr_or_return_null!(alg); + let result = ok_or_return_null!(C2paHasher::new(&alg)); + box_tracked!(result) +} + +/// Update the hasher with supplied data +/// +/// #Parameters +/// * hasher_ptr: point to C2paHasher from c2pa_hasher_from_alg. +/// * data_ptr: pointer to data to hash. +/// * data_len: length of data to hash. +/// +/// # Errors +/// Returns -1 if there were errors, otherwise returns 0. +/// The error string can be retrieved by calling c2pa_error. +/// +/// # Safety +/// hasher_ptr and data_ptr must not be NULL.. +/// +/// # Example +/// ```c +/// auto hasher = c2pa_hasher_from_alg("sha256"); +/// if (hasher == NULL) { +/// let error = c2pa_error(); +/// printf("Error: %s\n", error); +/// +/// auto data = std::vector buffer(1024); +/// +/// c2pa_hasher_update(hasher, (const uint8_t*)data.data(), 1024); +/// +/// c2pa_string_free(error); +/// } +/// ``` +#[no_mangle] +pub unsafe extern "C" fn c2pa_hasher_update( + hasher_ptr: *mut C2paHasher, + data_ptr: *const c_uchar, + data_len: usize, +) -> i64 { + ptr_or_return_int!(data_ptr); + + let hasher = deref_mut_or_return_int!(hasher_ptr, C2paHasher); + let hash_bytes = bytes_or_return_int!(data_ptr, data_len, "hash bytes"); + + hasher.update(hash_bytes); + + 0 +} +/// Finalize the hasher and return the hash bytes +/// +/// #Parameters +/// * hasher_ptr: point to C2paHasher from c2pa_hasher_from_alg. +/// * hash_bytes_ptr: pointer to receive the hash bytes pointer. +/// +/// # Errors +/// Returns -1 if there were errors, otherwise returns the length of the hash bytes. +/// The error string can be retrieved by calling c2pa_error. +/// +/// # Safety +/// hasher_ptr and hash_bytes_ptr must not be NULL. +/// The returned hash_bytes_ptr must be freed by calling c2pa_hashed_bytes_free. +/// +/// # Example +/// ```c +/// auto hasher = c2pa_hasher_from_alg("sha256"); +/// if (hasher == NULL) { +/// let error = c2pa_error(); +/// printf("Error: %s\n", error); +/// +/// auto data = std::vector buffer(1024); +/// +/// c2pa_hasher_update(hasher, (const uint8_t*)data.data(), 1024); +/// +/// const uint8_t* hash_bytes = NULL; +/// auto hash_len = c2pa_hasher_finalize(hasher, &hash_bytes); +/// +/// c2pa_string_free(error); +/// } +/// ``` +#[no_mangle] +pub unsafe extern "C" fn c2pa_hasher_finalize( + hasher_ptr: *mut C2paHasher, + hash_bytes_ptr: *mut *const c_uchar, +) -> i64 { + ptr_or_return_int!(hash_bytes_ptr); + + let hasher = deref_mut_or_return_int!(hasher_ptr, C2paHasher); + + let hash_bytes = hasher.finalize_reset(); + + let len = hash_bytes.len() as i64; + if !hash_bytes_ptr.is_null() { + *hash_bytes_ptr = to_c_bytes(hash_bytes); + } + len +} +/// Hash a u64 offset value into the hasher per C2PA BMFF hashing rules +/// +/// #Parameters +/// * hasher_ptr: point to C2paHasher from c2pa_hasher_from_alg. +/// * hash_offset: u64 offset value to hash. +/// +/// # Errors +/// Returns -1 if there were errors, otherwise returns 0. +/// The error string can be retrieved by calling c2pa_error. +/// +/// # Safety +/// hasher_ptr must not be NULL.. +/// +/// # Example +/// ```c +/// auto hasher = c2pa_hasher_from_alg("sha256"); +/// if (hasher == NULL) { +/// let error = c2pa_error(); +/// printf("Error: %s\n", error); +/// +/// auto offset = 12345678u64; +/// c2pa_hasher_hash_offset(hasher, offset); +/// +/// c2pa_string_free(error); +/// } +/// ``` +#[no_mangle] +pub unsafe extern "C" fn c2pa_hasher_hash_offset( + hasher_ptr: *mut C2paHasher, + hash_offset: u64, +) -> i64 { + let hasher = deref_mut_or_return_int!(hasher_ptr, C2paHasher); + let offset_be = hash_offset.to_be_bytes(); + + hasher.update(&offset_be); + + 0 +} + +/// Frees a C2paHasher allocated by Rust. +/// +/// +/// /// **Note**: This function is maintained for backward compatibility. New code should +/// use [`c2pa_free`] instead, which works for all pointer types. +/// +/// # Safety +/// The C2paHasher can only be freed once and is invalid after this call. +/// /// # Safety +/// hasher_ptr must not be NULL.. +/// +/// # Example +/// ```c +/// auto hasher = c2pa_hasher_from_alg("sha256"); +/// if (hasher == NULL) { +/// c2pa_string_free(error); +/// } +/// ``` +#[no_mangle] +pub unsafe extern "C" fn c2pa_hasher_free(hasher_ptr: *mut C2paHasher) { + cimpl_free!(hasher_ptr); +} + +/// Frees hash bytes pointer returned by c2pa_hasher_finalize allocated by Rust. +/// +/// # Safety +/// The hashed_bytes_ptr can only be freed once and is invalid after this call. +#[no_mangle] +pub unsafe extern "C" fn c2pa_hashed_bytes_free(hashed_bytes_ptr: *mut *const c_uchar) { + cimpl_free!(hashed_bytes_ptr); +} + +/// Creates a hashed placeholder from a Builder. +/// The placeholder is used to reserve size in an asset for later signing. +/// +/// # Parameters +/// * builder_ptr: pointer to a Builder. +/// * reserved_size: the size required for a signature from the intended signer. +/// * exclusion_map_json: is an optional pointer to use add BMFF exclusions +/// * manifest_bytes_ptr: pointer to a pointer to a c_uchar to return manifest_bytes. +/// +/// # Errors +/// Returns -1 if there were errors, otherwise returns the size of the manifest_bytes. +/// The error string can be retrieved by calling c2pa_error. +/// +/// # Safety +/// Reads from NULL-terminated C strings. +/// If manifest_bytes_ptr is not NULL, the returned value MUST be released by calling c2pa_manifest_bytes_free +/// and it is no longer valid after that call. +#[no_mangle] +pub unsafe extern "C" fn c2pa_builder_bmff_hashed_placeholder( + builder_ptr: *mut C2paBuilder, + reserved_size: usize, + exclusion_map_json: *const c_char, + manifest_bytes_ptr: *mut *const c_uchar, +) -> i64 { + ptr_or_return_int!(manifest_bytes_ptr); + let exclusion_map_str = cstr_option!(exclusion_map_json); + + let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder); + let result = + builder.get_bmff_hashed_manifest_placeholder(reserved_size, exclusion_map_str.as_deref()); + + let manifest_bytes = ok_or_return_int!(result); + + let len = manifest_bytes.len() as i64; + if !manifest_bytes_ptr.is_null() { + *manifest_bytes_ptr = to_c_bytes(manifest_bytes); + } + len +} + +/// Sign a Builder using the specified signer and bmff hash. +/// The hasher_ptr contains a C2paHasher that was used to accumulate the hash the file was written. +/// This is a low-level method for advanced use cases where the caller handles embedding the manifest in BMFF assets. +/// +/// # Parameters +/// * builder_ptr: pointer to a Builder. +/// * signer: pointer to a C2paSigner. +/// * hasher_ptr: pointer to C2paHasher. Can be null if you pass in asset. +/// * asset: pointer to C2paStream. If present it will used in lieu of the value passed in hasher_ptr. +/// * manifest_bytes_ptr: pointer to a pointer to a c_uchar to return manifest_bytes (optional, can be NULL). +/// +/// # Errors +/// Returns -1 if there were errors, otherwise returns the size of the manifest_bytes. +/// The error string can be retrieved by calling c2pa_error. +/// +/// # Safety +/// Reads from NULL-terminated C strings. +/// If manifest_bytes_ptr is not NULL, the returned value MUST be released by calling c2pa_manifest_bytes_free +/// and it is no longer valid after that call. +#[no_mangle] +pub unsafe extern "C" fn c2pa_builder_sign_bmff_hashed_embeddable( + builder_ptr: *mut C2paBuilder, + signer_ptr: *mut C2paSigner, + hasher_ptr: *mut C2paHasher, + asset_stream_ptr: *mut C2paStream, + manifest_bytes_ptr: *mut *const c_uchar, +) -> i64 { + ptr_or_return_int!(manifest_bytes_ptr); + + let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder); + let c2pa_signer = deref_mut_or_return_int!(signer_ptr, C2paSigner); + + let hash = if !asset_stream_ptr.is_null() { + let asset_stream = deref_mut_or_return_int!(asset_stream_ptr, C2paStream); + // generate the hash from the asset + if let Some((mut bmff_hash, label)) = builder.find_assertion_by_label::(BMFF_HASH) + { + let (_label, version, _instance) = parse_label(&label); + + bmff_hash.set_bmff_version(version); + + ok_or_return_int!(bmff_hash.gen_hash_from_stream(&mut *asset_stream)); + bmff_hash.hash().map_or(Vec::new(), |v| v.clone()) + } else { + return -1; + } + } else if !hasher_ptr.is_null() { + // grab the value from the user generated hash + let hasher = deref_mut_or_return_int!(hasher_ptr, C2paHasher); + hasher.finalize_reset() + } else { + return -1; + }; + + let result = builder.sign_bmff_hashed_embeddable(c2pa_signer.signer.as_ref(), &hash); + + let manifest_bytes = ok_or_return_int!(result); + + let len = manifest_bytes.len() as i64; + if !manifest_bytes_ptr.is_null() { + *manifest_bytes_ptr = to_c_bytes(manifest_bytes); + } + len +} + /// Creates a hashed placeholder from a Builder. /// The placeholder is used to reserve size in an asset for later signing. /// @@ -2863,7 +3158,146 @@ mod tests { assert!(!signer.is_null()); unsafe { c2pa_signer_free(signer) }; } + #[test] + fn test_bmff_embeddable() { + let manifest_def = r#"{ + "title": "Video Test", + "format": "video/mp4", + "claim_generator_info": [ + { + "name": "ffmpeg_sample_app", + "version": "1.0.0" + } + ], + "metadata": [ + { + "dateTime": "1985-04-12T23:20:50.52Z", + "my_custom_metadata": "my custom metatdata value" + } + ], + "ingredients": [ + { + "title": "Some Source Video", + "format": "video/mp4", + "instance_id": "12345", + "relationship": "inputTo", + "metadata": { + "dateTime": "1985-04-12T23:20:50.52Z", + "my_custom_metadata": "my custom metatdata value" + } + } + ], + "assertions": [ + { + "label": "c2pa.actions", + "data": { + "actions": [ + { + "action": "c2pa.created", + "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia", + "softwareAgent": { + "name": "ffmpeg_sample_app", + "version": "1.0.0" + }, + "description": "This video was created by ffmpeg_sample_app", + "when": "2025-04-22T17:25:28Z", + "parameters": { + "description": "This image was edited by Adobe Firefly" + }, + "softwareAgentIndex": 0 + } + ] + } + }, + { + "label": "c2pa.metadata", + "data": { + "@context" : { + "exif": "http://ns.adobe.com/exif/1.0/", + "exifEX": "http://cipa.jp/exif/1.0/", + "tiff": "http://ns.adobe.com/tiff/1.0/", + "Iptc4xmpExt": "http://iptc.org/std/Iptc4xmpExt/2008-02-29/", + "photoshop" : "http://ns.adobe.com/photoshop/1.0/" + }, + "photoshop:DateCreated": "Aug 31, 2022", + "Iptc4xmpExt:DigitalSourceType": "https://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture", + "exif:GPSVersionID": "2.2.0.0", + "exif:GPSLatitude": "39,21.102N", + "exif:GPSLongitude": "74,26.5737W", + "exif:GPSAltitudeRef": 0, + "exif:GPSAltitude": "100963/29890", + "exifEX:LensSpecification": { "@list": [ 1.55, 4.2, 1.6, 2.4 ] } + }, + "kind": "Json" + } + ] + }"#; + + let signer_def = r#"{ + "signer": { + "local": { + "alg": "ps256", + "sign_cert": "-----BEGIN CERTIFICATE-----\\n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\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\n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\\nwvq03sA=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\n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\\n-----END CERTIFICATE-----", + "private_key": "-----BEGIN PRIVATE KEY-----\\n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\\nG/dZR0Qj3j0T5F9GX719XpmzYbVFKIKPTsKF\\n-----END PRIVATE KEY-----\\n" + } + } + }"#; + + let manifest_def = CString::new(manifest_def.as_bytes()).unwrap(); + let signer_def = CString::new(signer_def.as_bytes()).unwrap(); + let format = CString::new("json").unwrap(); + + let data = "Some data to hash"; + let alg = CString::new("sha256").unwrap(); + let source_image = include_bytes!(fixture_path!("sample1.heic")); + let mut source_stream = TestStream::new(source_image.to_vec()); + + // set up full write simulate sequence + unsafe { + let hasher = c2pa_hasher_from_alg(alg.as_ptr()); + let _setting = c2pa_load_settings(signer_def.as_ptr(), format.as_ptr()); + let builder = c2pa_builder_from_json(manifest_def.as_ptr()); + let signer = c2pa_signer_from_settings(); + let reserve_size = c2pa_signer_reserve_size(signer); + + assert!(!hasher.is_null()); + assert!(!builder.is_null()); + assert!(!signer.is_null()); + + // get embedded placeholder + let mut placeholder_bytes = std::ptr::null(); + let placeholder_len = c2pa_builder_bmff_hashed_placeholder( + builder, + reserve_size as usize, + std::ptr::null(), + &mut placeholder_bytes, + ); + + // gen some hash values + let c = c2pa_hasher_update(hasher, data.as_ptr(), data.len()); + assert!(c == 0); + + // gen final manifest + let mut manifest_bytes = std::ptr::null(); + + let manifest_len = c2pa_builder_sign_bmff_hashed_embeddable( + builder, + signer, + hasher, + source_stream.as_ptr(), + &mut manifest_bytes, + ); + assert_ne!(manifest_len, -1); + assert_eq!(placeholder_len, manifest_len); + + c2pa_builder_free(builder); + c2pa_signer_free(signer); + c2pa_manifest_bytes_free(placeholder_bytes); + c2pa_manifest_bytes_free(manifest_bytes); + c2pa_hasher_free(hasher); + } + } #[test] fn test_c2pa_settings_new() { let settings = unsafe { c2pa_settings_new() }; diff --git a/sdk/src/assertions/bmff_hash.rs b/sdk/src/assertions/bmff_hash.rs index aea855c9f..a662f5bcd 100644 --- a/sdk/src/assertions/bmff_hash.rs +++ b/sdk/src/assertions/bmff_hash.rs @@ -35,7 +35,6 @@ use crate::{ }, asset_io::CAIRead, cbor_types::UriT, - settings::Settings, utils::{ hash_utils::{ concat_and_hash, hash_stream_by_alg, vec_compare, verify_stream_by_alg, HashRange, @@ -51,6 +50,15 @@ use crate::{ const ASSERTION_CREATION_VERSION: usize = 3; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +pub struct UserHashInfo { + pub xpath: String, // path name of top level box, the path for the c2pa box should be "/c2pa". + pub start: u64, // offset to start of the boxes hash (if 0 then it is the beginning of the box) + pub end: u64, // end of the boxes hash (if 0 hash until box end) + pub need_box_loc_hash: bool, // if true you must has the file offset of the box, the position should be a big-endian 64 bit number + pub excluded: bool, // the box should not be hashed +} + +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct ExclusionsMap { pub xpath: String, pub length: Option, @@ -241,7 +249,7 @@ pub struct BmffMerkleMap { pub hashes: Option, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct DataMap { pub offset: u64, #[serde(with = "serde_bytes")] @@ -305,6 +313,349 @@ impl BmffHash { } } + pub fn add_merkle_map_for_mdats( + &mut self, + asset_stream: &mut dyn CAIRead, + merkle_chunk_size: usize, + max_proofs: usize, + ) -> crate::error::Result<()> { + // enable flat flat files with Merkle trees if desired + // we do this here because the UUID boxes must be in place + // for the later hash generation + // mdat boxes are excluded when using Merkle hashing + let mut mdat = ExclusionsMap::new("/mdat".to_owned()); + let subset_mdat = SubsetMap { + offset: 16, + length: 0, + }; + let subset_mdat_vec = vec![subset_mdat]; + mdat.subset = Some(subset_mdat_vec); + self.exclusions.push(mdat); + + // get the merkle hashes for the mdat boxes + let boxes = read_bmff_c2pa_boxes(asset_stream)?; + let mut mdat_boxes = boxes.box_infos.clone(); + mdat_boxes.retain(|b| b.path == "mdat"); + + let mut merkle_maps = Vec::new(); + let mut uuid_boxes = Vec::new(); + + for (index, mdat_box) in mdat_boxes.iter().enumerate() { + let fixed_block_size = if merkle_chunk_size > 0 { + Some(1024 * merkle_chunk_size as u64) + } else { + None + }; + + let mut merkle_map = MerkleMap { + unique_id: 0, + local_id: index, + count: 0, + alg: self.alg.clone(), + init_hash: None, + hashes: VecByteBuf(Vec::new()), + fixed_block_size, + variable_block_sizes: None, + }; + + // build list of ordered UUID merkle boxes + let mut current_uuid_boxes = self.create_merkle_map_for_mdat_box( + asset_stream, + mdat_box, + &mut merkle_map, + max_proofs, + )?; + uuid_boxes.append(&mut current_uuid_boxes); + + merkle_maps.push(merkle_map); + } + + if merkle_maps.is_empty() { + return Ok(()); + } + + self.merkle = Some(merkle_maps); + if !uuid_boxes.is_empty() { + self.merkle_uuid_boxes = Some(uuid_boxes.into_iter().flatten().collect::>()); + + // calculate the insertion point for the UUID boxes after the last mdat box + let last_mdat_box = mdat_boxes + .last() + .ok_or(Error::BadParam("No mdat boxes found".to_string()))?; + self.merkle_uuid_boxes_insertion_point = last_mdat_box.end(); + + // if there are existing Merkle UUID boxes we want to overwrite those + if let Some(last_uuid_box) = boxes.bmff_merkle_box_infos.last() { + self.merkle_replacement_range = last_mdat_box.end() - last_uuid_box.end(); + } + } + Ok(()) + } + + // Extra BMFF exclusion ranges can be added as needed, the exclusions Vec will be updated + // to only contain unique exclusions that were added + pub fn add_exclusions(&mut self, exclusions: &mut Vec) { + exclusions.retain(|e| !self.exclusions().contains(e)); + self.exclusions.append(exclusions); + + exclusions.clear(); + + // update the exclusions + exclusions.clone_from(&self.exclusions().to_vec()); + } + + // Adds default exclusion ranges for BMFF hashes. Add as needed. + pub fn set_default_exclusions(&mut self) -> &[ExclusionsMap] { + let exclusions = &mut self.exclusions; + + let cp2_id: [u8; 16] = [ + 216, 254, 195, 214, 27, 14, 72, 60, 146, 151, 88, 40, 135, 126, 196, 129, + ]; + + // jumbf exclusion + if !exclusions.iter().any(|e| match &e.data { + Some(d) => d.iter().any(|data_map| data_map.value == cp2_id.to_vec()), + None => false, + }) { + let mut uuid = ExclusionsMap::new("/uuid".to_owned()); + let data = DataMap { + offset: 8, + value: cp2_id.to_vec(), // C2PA identifier + }; + let data_vec = vec![data]; + uuid.data = Some(data_vec); + exclusions.push(uuid); + } + + // /ftyp exclusion + if !exclusions.iter().any(|e| e.xpath == "/ftyp") { + let ftyp = ExclusionsMap::new("/ftyp".to_owned()); + exclusions.push(ftyp); + } + + // /mfra exclusion + if !exclusions.iter().any(|e| e.xpath == "/mfra") { + let mfra = ExclusionsMap::new("/mfra".to_owned()); + exclusions.push(mfra); + } + + /* no longer mandatory + // meta/iloc exclusion + let iloc = ExclusionsMap::new("/meta/iloc".to_owned()); + exclusions.push(iloc); + + // /mfra/tfra exclusion + let tfra = ExclusionsMap::new("/mfra/tfra".to_owned()); + exclusions.push(tfra); + + // /moov/trak/mdia/minf/stbl/stco exclusion + let mut stco = ExclusionsMap::new("/moov/trak/mdia/minf/stbl/stco".to_owned()); + let subset_stco = SubsetMap { + offset: 16, + length: 0, + }; + let subset_stco_vec = vec![subset_stco]; + stco.subset = Some(subset_stco_vec); + exclusions.push(stco); + + // /moov/trak/mdia/minf/stbl/co64 exclusion + let mut co64 = ExclusionsMap::new("/moov/trak/mdia/minf/stbl/co64".to_owned()); + let subset_co64 = SubsetMap { + offset: 16, + length: 0, + }; + let subset_co64_vec = vec![subset_co64]; + co64.subset = Some(subset_co64_vec); + exclusions.push(co64); + + // /moof/traf/tfhd exclusion + let mut tfhd = ExclusionsMap::new("/moof/traf/tfhd".to_owned()); + let subset_tfhd = SubsetMap { + offset: 16, + length: 8, + }; + let subset_tfhd_vec = vec![subset_tfhd]; + tfhd.subset = Some(subset_tfhd_vec); + tfhd.flags = Some(ByteBuf::from([1, 0, 0])); + exclusions.push(tfhd); + + // /moof/traf/trun exclusion + let mut trun = ExclusionsMap::new("/moof/traf/trun".to_owned()); + let subset_trun = SubsetMap { + offset: 16, + length: 4, + }; + let subset_trun_vec = vec![subset_trun]; + trun.subset = Some(subset_trun_vec); + trun.flags = Some(ByteBuf::from([1, 0, 0])); + exclusions.push(trun); + */ + + &self.exclusions + } + + pub fn add_place_holder_hash(&mut self) -> crate::error::Result<()> { + // make sure hash space is reserved + if let Some(alg) = &self.alg { + match alg.as_str() { + "sha256" => self.set_hash([0u8; 32].to_vec()), + "sha384" => self.set_hash([0u8; 48].to_vec()), + "sha512" => self.set_hash([0u8; 64].to_vec()), + _ => return Err(Error::UnsupportedType), + } + } + Ok(()) + } + + // get regions to hash based on the list of top level boxes in file order + pub fn box_list_to_user_exclusions(&self, _box_paths: &[String]) -> Result> { + let uhis = Vec::new(); + + Ok(uhis) + + /* + for box_path in box_paths { + // c2pa exclusion + if box_path == "/c2pa" { + uhis.push(UserHashInfo { + xpath: "/c2pa".to_string(), + start: 0, + end: 0, + need_box_loc_hash: false, + excluded: true, + }); + + continue; + } + + if let Some(exclusion) = self.exclusions.iter().find(|e| { + &e.xpath == box_path + }) { + for box_token in box_token_list { + let box_info = &bmff_tree[*box_token].data; + + let box_start = box_info.offset; + let box_length = box_info.size; + + let exclusion_start = box_start; + let exclusion_length = box_length; + + // adjust exclusion bounds as needed + + // check the length + if let Some(desired_length) = bmff_exclusion.length { + if desired_length != box_length { + continue; + } + } + + // check the version + if let Some(desired_version) = bmff_exclusion.version { + if let Some(box_version) = box_info.version { + if desired_version != box_version { + continue; + } + } + } + + // check the flags + if let Some(desired_flag_bytes) = &bmff_exclusion.flags { + let mut temp_bytes = [0u8; 4]; + if desired_flag_bytes.len() >= 3 { + temp_bytes[0] = desired_flag_bytes[0]; + temp_bytes[1] = desired_flag_bytes[1]; + temp_bytes[2] = desired_flag_bytes[2]; + } + let desired_flags = u32::from_be_bytes(temp_bytes); + + if let Some(box_flags) = box_info.flags { + let exact = bmff_exclusion.exact.unwrap_or(true); + + if exact { + if desired_flags != box_flags { + continue; + } + } else { + // bitwise match + if (desired_flags | box_flags) != desired_flags { + continue; + } + } + } + } + + // check data match + if let Some(data_map_vec) = &bmff_exclusion.data { + let mut should_add = true; + + for data_map in data_map_vec { + // move to the start of exclusion + skip_bytes_to(reader, box_start + data_map.offset)?; + + // match the data + let buf = reader.read_to_vec(data_map.value.len() as u64)?; + + // does not match so skip + if !vec_compare(&data_map.value, &buf) { + should_add = false; + break; + } + } + if !should_add { + continue; + } + } + + // reduce range if desired + if let Some(subset_vec) = &bmff_exclusion.subset { + for subset in subset_vec { + // if the subset offset is past the end of the box, skip + if subset.offset > exclusion_length { + continue; + } + + let new_start = exclusion_start + subset.offset; + let new_length = if subset.length == 0 { + exclusion_length - subset.offset + } else { + min(subset.length, exclusion_length - subset.offset) + }; + + let exclusion = HashRange::new(new_start, new_length); + + exclusions.push(exclusion); + } + } else { + // exclude box in its entirty + let exclusion = HashRange::new(exclusion_start, exclusion_length); + + exclusions.push(exclusion); + + // for BMFF V2 hashes we do not add hash offsets for top level boxes + // that are completely excluded, so remove from BMFF V2 hash offset calc + if let Some(pos) = tl_offsets.iter().position(|x| *x == exclusion_start) { + tl_offsets.remove(pos); + } + } + } + } + } + + // add remaining top level offsets to be included when generating BMFF V2 hashes + // note: this is technically not an exclusion but a replacement with a new range of bytes to be hashed + if bmff_v2 { + for tl_start in tl_offsets { + let mut exclusion = HashRange::new(tl_start, 1u64); + exclusion.set_bmff_offset(tl_start); + + exclusions.push(exclusion); + } + } + + Ok(uhi) + */ + } + pub fn exclusions(&self) -> &[ExclusionsMap] { self.exclusions.as_ref() } @@ -345,7 +696,7 @@ impl BmffHash { self.bmff_version } - pub(crate) fn set_bmff_version(&mut self, version: usize) { + pub fn set_bmff_version(&mut self, version: usize) { self.bmff_version = version; } @@ -565,7 +916,6 @@ impl BmffHash { // start the verification reader.rewind()?; let size = stream_len(reader)?; - let curr_alg = match &self.alg { Some(a) => a.clone(), None => match alg { @@ -1342,10 +1692,8 @@ impl BmffHash { reader: &mut dyn CAIRead, box_info: &BoxInfoLite, merkle_map: &mut MerkleMap, - settings: &Settings, + max_proofs: usize, ) -> crate::Result>> { - let max_proofs = settings.core.merkle_tree_max_proofs; - // build the Merkle tree let m_tree = self.create_merkle_tree_for_merkle_map(reader, box_info, merkle_map)?; let num_leaves = m_tree.leaves.len(); @@ -1588,6 +1936,8 @@ impl BmffHash { impl AssertionCbor for BmffHash {} +//impl AssertionJson for BmffHash {} + impl AssertionBase for BmffHash { const LABEL: &'static str = Self::LABEL; const VERSION: Option = Some(ASSERTION_CREATION_VERSION); diff --git a/sdk/src/asset_handlers/bmff_io.rs b/sdk/src/asset_handlers/bmff_io.rs index 44c770233..2820bcfc9 100644 --- a/sdk/src/asset_handlers/bmff_io.rs +++ b/sdk/src/asset_handlers/bmff_io.rs @@ -26,7 +26,7 @@ use crate::{ assertions::{BmffMerkleMap, ExclusionsMap}, asset_io::{ rename_or_move, AssetIO, AssetPatch, CAIRead, CAIReadWrite, CAIReader, CAIWriter, - HashObjectPositions, RemoteRefEmbed, RemoteRefEmbedType, + ComposedManifestRef, HashObjectPositions, RemoteRefEmbed, RemoteRefEmbedType, }, error::{Error, Result}, status_tracker::{ErrorBehavior, StatusTracker}, @@ -1821,6 +1821,10 @@ impl AssetIO for BmffIO { Some(self) } + fn composed_data_ref(&self) -> Option<&dyn ComposedManifestRef> { + Some(self) + } + fn supported_types(&self) -> &[&str] { &SUPPORTED_TYPES } @@ -2318,6 +2322,14 @@ impl AssetPatch for BmffIO { } } +impl ComposedManifestRef for BmffIO { + fn compose_manifest(&self, manifest_data: &[u8], _format: &str) -> Result> { + let mut new_c2pa_box: Vec = Vec::with_capacity(manifest_data.len() * 2); + write_c2pa_box(&mut new_c2pa_box, manifest_data, MANIFEST, &[], 0)?; + Ok(new_c2pa_box) + } +} + impl RemoteRefEmbed for BmffIO { #[allow(unused_variables)] fn embed_reference( diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index ca5fb17db..764510c6e 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -32,8 +32,10 @@ use crate::assertions::CreativeWork; use crate::{ assertion::{AssertionBase, AssertionDecodeError}, assertions::{ - c2pa_action, labels, Action, ActionTemplate, Actions, AssertionMetadata, BmffHash, BoxHash, - DataHash, DigitalSourceType, EmbeddedData, Exif, Metadata, SoftwareAgent, Thumbnail, + c2pa_action, + labels::{self, parse_label}, + Action, ActionTemplate, Actions, AssertionMetadata, BmffHash, BoxHash, DataHash, + DigitalSourceType, EmbeddedData, ExclusionsMap, Exif, Metadata, SoftwareAgent, Thumbnail, TimeStamp, User, UserCbor, }, claim::Claim, @@ -1222,6 +1224,16 @@ impl Builder { Builder::new().with_archive(stream) } + pub fn find_assertion_by_label(&self, label: &str) -> Option<(T, String)> { + for assertion_def in &self.definition.assertions { + if assertion_def.label().contains(label) { + let out: T = assertion_def.to_assertion().ok()?; + return Some((out, assertion_def.label().to_string())); + } + } + None + } + // Convert a Manifest into a Claim fn to_claim(&self) -> Result { // utility function to add created or gathered assertions @@ -1364,7 +1376,9 @@ impl Builder { let mut found_actions = false; // add any additional assertions for manifest_assertion in &definition.assertions { - match manifest_assertion.label() { + // match the root labels independently of version/instance + let (match_label, version, _instance) = parse_label(manifest_assertion.label()); + match match_label { l if l.starts_with(Actions::LABEL) => { found_actions = true; @@ -1471,7 +1485,8 @@ impl Builder { claim.add_assertion(&data_hash) } BmffHash::LABEL => { - let bmff_hash: BmffHash = manifest_assertion.to_assertion()?; + let mut bmff_hash: BmffHash = manifest_assertion.to_assertion()?; + bmff_hash.set_bmff_version(version); claim.add_assertion(&bmff_hash) } Metadata::LABEL => { @@ -1904,6 +1919,82 @@ impl Builder { } } + /// Create a placeholder for a bmff hashed manifest. + /// + /// This is only used for applications doing their own BMFF hash asset management. + /// + /// # Arguments + /// * `reserve_size` - The size to reserve for the signature (taken from the signer). + /// * `bmff_exclusions_json` - The format of the t. + /// # Returns + /// * The bytes of the `c2pa_manifest` placeholder. + /// # Errors + /// * Returns an [`Error`] if the placeholder cannot be created. + pub fn get_bmff_hashed_manifest_placeholder( + &mut self, + reserve_size: usize, + bmff_exclusions_json: Option<&str>, + ) -> Result> { + let mut bmff_exclusions: Vec = + if let Some(bmff_exclusions_json) = bmff_exclusions_json { + serde_json::from_str(bmff_exclusions_json)? + } else { + Vec::new() + }; + + // create the default BMFF hash + let mut bmff_hash = BmffHash::new("jumbf_box", "sha256", None); + bmff_hash.set_default_exclusions(); + + // add user defined exclusions + bmff_hash.add_exclusions(&mut bmff_exclusions); + + // make sure we reserve space for the hash + bmff_hash.add_place_holder_hash()?; + + // add BMFF assertion + let assertion_label = bmff_hash.to_assertion()?.label(); + self.add_assertion(&assertion_label, &bmff_hash)?; + + let mut store = self.to_store()?; + let placeholder = store.get_bmff_hashed_manifest_placeholder(reserve_size)?; + Ok(placeholder) + } + + /// Create a signed data hashed embeddable manifest using a supplied signer. + /// + /// This is used to create a manifest that can be embedded into a stream. + /// It allows the caller to do the embedding. + /// You must call `get_bmff_hashed_manifest_placeholder` first to create the placeholder. + /// The placeholder is then injected into the asset before calculating hashes + /// You must generate the hashes. + /// + /// # Arguments + /// * `signer` - The signer to use. + /// * `hash` - Hash calculated by the caller. + /// # Returns + /// * The bytes of the `c2pa_manifest` that was created (prep-formatted). + #[async_generic(async_signature( + &mut self, + signer: &dyn AsyncSigner, + hash: &[u8], + ))] + pub fn sign_bmff_hashed_embeddable( + &mut self, + signer: &dyn Signer, + hash: &[u8], + ) -> Result> { + let mut store = self.to_store()?; + + if _sync { + store.get_bmff_hashed_embeddable_manifest(hash, signer, &self.context) + } else { + store + .get_bmff_hashed_embeddable_manifest_async(hash, signer, &self.context) + .await + } + } + /// Create a placeholder for a hashed data manifest. /// /// This is only used for applications doing their own data_hashed asset management. @@ -1939,7 +2030,7 @@ impl Builder { /// /// This is used to create a manifest that can be embedded into a stream. /// It allows the caller to do the embedding. - /// You must call `data_hashed` placeholder first to create the placeholder. + /// You must call `data_hashed_placeholder` first to create the placeholder. /// The placeholder is then injected into the asset before calculating hashes /// You must either pass a source stream to generate the hashes or provide the hashes. /// diff --git a/sdk/src/claim.rs b/sdk/src/claim.rs index bc6015a95..76ecf0bdc 100644 --- a/sdk/src/claim.rs +++ b/sdk/src/claim.rs @@ -3320,6 +3320,13 @@ impl Claim { self.assertions_by_type(&dummy_bmff_hash, None) } + pub fn data_hash_assertions(&self) -> Vec<&ClaimAssertion> { + // add in an BMFF hashes + let dummy_hash_data = AssertionData::Cbor(Vec::new()); + let dummy_data_hash = Assertion::new(assertions::labels::DATA_HASH, None, dummy_hash_data); + self.assertions_by_type(&dummy_data_hash, None) + } + pub fn box_hash_assertions(&self) -> Vec<&ClaimAssertion> { // add in an BMFF hashes let dummy_box_data = AssertionData::Cbor(Vec::new()); diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index d394eb66c..7f23e5bbc 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -250,7 +250,7 @@ pub use crypto::raw_signature::SigningAlg; pub use error::{Error, Result}; #[doc(hidden)] pub use external_manifest::ManifestPatchCallback; -pub use hash_utils::{hash_stream_by_alg, HashRange}; +pub use hash_utils::{hash_by_alg, hash_stream_by_alg, HashRange, Hasher}; pub use hashed_uri::HashedUri; pub use ingredient::Ingredient; #[cfg(feature = "file_io")] diff --git a/sdk/src/store.rs b/sdk/src/store.rs index 2a47afcf4..baec9e1b0 100644 --- a/sdk/src/store.rs +++ b/sdk/src/store.rs @@ -29,10 +29,9 @@ use crate::{ assertion::{Assertion, AssertionBase, AssertionData, AssertionDecodeError}, assertions::{ labels::{self, CLAIM}, - BmffHash, CertificateStatus, DataBox, DataHash, DataMap, ExclusionsMap, Ingredient, - MerkleMap, Relationship, SubsetMap, TimeStamp, User, UserCbor, VecByteBuf, + BmffHash, CertificateStatus, DataBox, DataHash, Ingredient, Relationship, TimeStamp, User, + UserCbor, }, - asset_handlers::bmff_io::read_bmff_c2pa_boxes, asset_io::{ CAIRead, CAIReadWrite, HashBlockObjectType, HashObjectPositions, RemoteRefEmbedType, }, @@ -2269,171 +2268,126 @@ impl Store { Ok(hashes) } - fn generate_bmff_data_hash_for_stream( - asset_stream: &mut dyn CAIRead, - alg: &str, - settings: &Settings, - ) -> Result { + fn generate_bmff_data_hash_for_stream(alg: &str) -> Result { // The spec has mandatory BMFF exclusion ranges for certain atoms. // The function makes sure those are included. let mut dh = BmffHash::new("jumbf manifest", alg, None); - let exclusions = dh.exclusions_mut(); - - // jumbf exclusion - let mut uuid = ExclusionsMap::new("/uuid".to_owned()); - let data = DataMap { - offset: 8, - value: vec![ - 216, 254, 195, 214, 27, 14, 72, 60, 146, 151, 88, 40, 135, 126, 196, 129, - ], // C2PA identifier - }; - let data_vec = vec![data]; - uuid.data = Some(data_vec); - exclusions.push(uuid); - - // ftyp exclusion - let ftyp = ExclusionsMap::new("/ftyp".to_owned()); - exclusions.push(ftyp); - - // /mfra/ exclusion - let mfra = ExclusionsMap::new("/mfra".to_owned()); - exclusions.push(mfra); - - /* no longer mandatory - // meta/iloc exclusion - let iloc = ExclusionsMap::new("/meta/iloc".to_owned()); - exclusions.push(iloc); - - // /mfra/tfra exclusion - let tfra = ExclusionsMap::new("/mfra/tfra".to_owned()); - exclusions.push(tfra); - - // /moov/trak/mdia/minf/stbl/stco exclusion - let mut stco = ExclusionsMap::new("/moov/trak/mdia/minf/stbl/stco".to_owned()); - let subset_stco = SubsetMap { - offset: 16, - length: 0, - }; - let subset_stco_vec = vec![subset_stco]; - stco.subset = Some(subset_stco_vec); - exclusions.push(stco); - - // /moov/trak/mdia/minf/stbl/co64 exclusion - let mut co64 = ExclusionsMap::new("/moov/trak/mdia/minf/stbl/co64".to_owned()); - let subset_co64 = SubsetMap { - offset: 16, - length: 0, - }; - let subset_co64_vec = vec![subset_co64]; - co64.subset = Some(subset_co64_vec); - exclusions.push(co64); - - // /moof/traf/tfhd exclusion - let mut tfhd = ExclusionsMap::new("/moof/traf/tfhd".to_owned()); - let subset_tfhd = SubsetMap { - offset: 16, - length: 8, - }; - let subset_tfhd_vec = vec![subset_tfhd]; - tfhd.subset = Some(subset_tfhd_vec); - tfhd.flags = Some(ByteBuf::from([1, 0, 0])); - exclusions.push(tfhd); - - // /moof/traf/trun exclusion - let mut trun = ExclusionsMap::new("/moof/traf/trun".to_owned()); - let subset_trun = SubsetMap { - offset: 16, - length: 4, - }; - let subset_trun_vec = vec![subset_trun]; - trun.subset = Some(subset_trun_vec); - trun.flags = Some(ByteBuf::from([1, 0, 0])); - exclusions.push(trun); - */ + dh.set_default_exclusions(); - // enable flat flat files with Merkle trees if desired - // we do this here because the UUID boxes must be in place - // for the later hash generation - if let Some(merkle_chunk_size) = settings.core.merkle_tree_chunk_size_in_kb { - // mdat boxes are excluded when using Merkle hashing - let mut mdat = ExclusionsMap::new("/mdat".to_owned()); - let subset_mdat = SubsetMap { - offset: 16, - length: 0, - }; - let subset_mdat_vec = vec![subset_mdat]; - mdat.subset = Some(subset_mdat_vec); - exclusions.push(mdat); + // fill in temporary hash + match alg { + "sha256" => dh.set_hash([0u8; 32].to_vec()), + "sha384" => dh.set_hash([0u8; 48].to_vec()), + "sha512" => dh.set_hash([0u8; 64].to_vec()), + _ => return Err(Error::UnsupportedType), + } - // get the merkle hashes for the mdat boxes - let boxes = read_bmff_c2pa_boxes(asset_stream)?; - let mut mdat_boxes = boxes.box_infos.clone(); - mdat_boxes.retain(|b| b.path == "mdat"); + Ok(dh) + } - let mut merkle_maps = Vec::new(); - let mut uuid_boxes = Vec::new(); + fn generate_bmff_mdat_hashes( + asset_stream: &mut dyn CAIRead, + bmff_hash: &mut BmffHash, + settings: &Settings, + ) -> Result<()> { + if let Some(merkle_chunk_size) = settings.core.merkle_tree_chunk_size_in_kb { + bmff_hash.add_merkle_map_for_mdats( + asset_stream, + merkle_chunk_size, + settings.core.merkle_tree_max_proofs, + )?; + } + Ok(()) + } - for (index, mdat_box) in mdat_boxes.iter().enumerate() { - let fixed_block_size = if merkle_chunk_size > 0 { - Some(1024 * merkle_chunk_size as u64) - } else { - None - }; + /// This function is used to pre-generate a manifest with place holders for the final + /// BmffHash and Manifest Signature. The BmffHash will reserve space for the hash value + /// The Exclusion ranges are passed in and cocatinated with the default exclusions. + /// The Signature box reserved size is based on the size required by + /// the Signer you plan to use. This function is used + /// in conjunction with `get_bmff_hashed_embeddable_manifest`. The manifest returned + /// from `get_bmff_hashed_embeddable_manifest` will have a size that matches this function. + /// The bmff_exclusion list is updated to reflect the current set of exclusions + pub fn get_bmff_hashed_manifest_placeholder(&mut self, reserve_size: usize) -> Result> { + let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?; - let mut merkle_map = MerkleMap { - unique_id: 0, - local_id: index, - count: 0, - alg: Some(alg.to_string()), - init_hash: None, - hashes: VecByteBuf(Vec::new()), - fixed_block_size, - variable_block_sizes: None, - }; + // if user did not supply a hash + if pc.hash_assertions().is_empty() { + // create placeholder BmffHash assertion + let mut bmff_hash: BmffHash = BmffHash::new("jumbf manifest", pc.alg(), None); + bmff_hash.set_default_exclusions(); - // build list of ordered UUID merkle boxes - let mut current_uuid_boxes = dh.create_merkle_map_for_mdat_box( - asset_stream, - mdat_box, - &mut merkle_map, - settings, - )?; - uuid_boxes.append(&mut current_uuid_boxes); + // fill in temporary hash + bmff_hash.add_place_holder_hash()?; - merkle_maps.push(merkle_map); - } + // insert new BMFF hash + pc.add_assertion(&bmff_hash)?; + } - if merkle_maps.is_empty() { - return Err(Error::BadParam("No mdat boxes found".to_string())); - } + let jumbf_bytes = self.to_jumbf_internal(reserve_size)?; + + let composed = Self::get_composed_manifest(&jumbf_bytes, "mp4")?; - dh.merkle = Some(merkle_maps); - if !uuid_boxes.is_empty() { - dh.merkle_uuid_boxes = Some(uuid_boxes.into_iter().flatten().collect::>()); + Ok(composed) + } - // calculate the insertion point for the UUID boxes after the last mdat box - let last_mdat_box = mdat_boxes - .last() - .ok_or(Error::BadParam("No mdat boxes found".to_string()))?; - dh.merkle_uuid_boxes_insertion_point = last_mdat_box.end(); + fn prep_bmff_embeddable_store(&mut self, reserve_size: usize, hash: &[u8]) -> Result> { + let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?; - // if there are existing Merkle UUID boxes we want to overwrite those - if let Some(last_uuid_box) = boxes.bmff_merkle_box_infos.last() { - dh.merkle_replacement_range = last_mdat_box.end() - last_uuid_box.end(); - } - } + // make sure there are data hashes present before generating + let bmff_hashes = pc.bmff_hash_assertions(); + if bmff_hashes.is_empty() { + return Err(Error::BadParam( + "Claim must have hash binding assertion".to_string(), + )); } - // fill in temporary hash - match alg { - "sha256" => dh.set_hash([0u8; 32].to_vec()), - "sha384" => dh.set_hash([0u8; 48].to_vec()), - "sha512" => dh.set_hash([0u8; 64].to_vec()), - _ => return Err(Error::UnsupportedType), + if !pc.box_hash_assertions().is_empty() || !pc.data_hash_assertions().is_empty() { + return Err(Error::BadParam("Only BMFFHash allowed".to_string())); } - Ok(dh) + // update the hash value + let mut bmff_hash = BmffHash::from_assertion(bmff_hashes[0].assertion())?; + bmff_hash.set_hash(hash.to_vec()); + pc.update_bmff_hash(bmff_hash)?; + + self.to_jumbf_internal(reserve_size) + } + + /// Returns a finalized, signed manifest. The manifest are only supported + /// for cases when the client has calculated the BMFF hash. The BMFFFHash placeholder assertion will be adjusted to + /// contain the correct values. + /// It is an error if `get_bmff_hashed_manifest_placeholder` was not called first + /// as this call inserts the BMFFHash placeholder assertion to reserve space for the + /// actual hash values. + #[async_generic(async_signature( + &mut self, + hash: &[u8], + signer: &dyn AsyncSigner, + context: &Context, + ))] + pub fn get_bmff_hashed_embeddable_manifest( + &mut self, + hash: &[u8], + signer: &dyn Signer, + context: &Context, + ) -> Result> { + let mut jumbf_bytes = self.prep_bmff_embeddable_store(signer.reserve_size(), hash)?; + + // sign contents + let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?; + + let sig = if _sync { + self.sign_claim(pc, signer, signer.reserve_size(), context.settings())? + } else { + self.sign_claim_async(pc, signer, signer.reserve_size(), context.settings()) + .await? + }; + + let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); + + self.finish_embeddable_store(&sig, &sig_placeholder, &mut jumbf_bytes, "mp4") } /// This function is used to pre-generate a manifest with place holders for the final @@ -2777,10 +2731,7 @@ impl Store { let mut data; // 2) Get hash ranges if needed - let mut asset_stream = std::fs::File::open(asset_path)?; - - let mut bmff_hash = - Store::generate_bmff_data_hash_for_stream(&mut asset_stream, pc.alg(), settings)?; + let mut bmff_hash = Store::generate_bmff_data_hash_for_stream(pc.alg())?; bmff_hash.clear_hash(); if pc.version() < 2 { @@ -3156,21 +3107,26 @@ impl Store { if is_bmff { // 2) Get hash ranges if needed, do not generate for update manifests - if !pc.update_manifest() { + let mut needs_hash = false; + if !pc.update_manifest() && pc.bmff_hash_assertions().is_empty() { intermediate_stream.rewind()?; - let mut bmff_hash = Store::generate_bmff_data_hash_for_stream( - &mut intermediate_stream, - pc.alg(), - settings, - )?; + let mut bmff_hash = Store::generate_bmff_data_hash_for_stream(pc.alg())?; if pc.version() < 2 { bmff_hash.set_bmff_version(2); // backcompat support } - // insert UUID boxes at the correct location if required + // add Merkle mdats if requested + Store::generate_bmff_mdat_hashes( + &mut intermediate_stream, + &mut bmff_hash, + settings, + )?; + + // insert Merkle UUID boxes at the correct location if required if let Some(merkle_uuid_boxes) = &bmff_hash.merkle_uuid_boxes { let mut temp_stream = io_utils::stream_with_fs_fallback(threshold); + intermediate_stream.rewind()?; insert_data_at( &mut intermediate_stream, @@ -3183,8 +3139,9 @@ impl Store { temp_stream.rewind()?; intermediate_stream = temp_stream; } - pc.add_assertion(&bmff_hash)?; + + needs_hash = true; } // 3) Generate in memory CAI jumbf block @@ -3208,7 +3165,7 @@ impl Store { if !pc.update_manifest() { let bmff_hashes = pc.bmff_hash_assertions(); - if !bmff_hashes.is_empty() { + if !bmff_hashes.is_empty() && needs_hash { let mut bmff_hash = BmffHash::from_assertion(bmff_hashes[0].assertion())?; output_stream.rewind()?; diff --git a/sdk/src/utils/hash_utils.rs b/sdk/src/utils/hash_utils.rs index 37823ad6f..a055d72e7 100644 --- a/sdk/src/utils/hash_utils.rs +++ b/sdk/src/utils/hash_utils.rs @@ -115,6 +115,26 @@ impl Hasher { SHA512(d) => d.finalize().to_vec(), } } + + pub fn finalize_reset(&mut self) -> Vec { + use Hasher::*; + + // return the hash and leave the Hasher open and reset + match self { + SHA256(ref mut d) => d.finalize_reset().to_vec(), + SHA384(ref mut d) => d.finalize_reset().to_vec(), + SHA512(ref mut d) => d.finalize_reset().to_vec(), + } + } + + pub fn new(alg: &str) -> Result { + match alg { + "sha256" => Ok(Hasher::SHA256(Sha256::new())), + "sha384" => Ok(Hasher::SHA384(Sha384::new())), + "sha512" => Ok(Hasher::SHA512(Sha512::new())), + _ => Err(Error::UnsupportedType), + } + } } // Return hash bytes for desired hashing algorithm.