data_hash.rs (9343B)
1 // Copyright 2023 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 // Example code (in unit test) for how you might use client DataHash values. This allows clients 15 // to perform the manifest embedding and optionally the hashing 16 17 #[cfg(not(target_arch = "wasm32"))] 18 use std::{ 19 io::{Read, Seek, Write}, 20 path::PathBuf, 21 }; 22 23 #[cfg(not(target_arch = "wasm32"))] 24 use c2pa::{ 25 assertions::{c2pa_action, Action, Actions, CreativeWork, DataHash, Exif, SchemaDotOrgPerson}, 26 create_signer, hash_stream_by_alg, HashRange, Ingredient, Manifest, ManifestStore, SigningAlg, 27 }; 28 29 fn main() { 30 println!("DataHash demo"); 31 32 #[cfg(not(target_arch = "wasm32"))] 33 user_data_hash_with_sdk_hashing(); 34 35 #[cfg(not(target_arch = "wasm32"))] 36 user_data_hash_with_user_hashing(); 37 } 38 39 #[cfg(not(target_arch = "wasm32"))] 40 fn user_data_hash_with_sdk_hashing() { 41 const GENERATOR: &str = "test_app/0.1"; 42 43 // You will often implement your own Signer trait to perform on device signing 44 let signcert_path = "sdk/tests/fixtures/certs/es256.pub"; 45 let pkey_path = "sdk/tests/fixtures/certs/es256.pem"; 46 let signer = 47 create_signer::from_files(signcert_path, pkey_path, SigningAlg::Es256, None).unwrap(); 48 49 let src = "sdk/tests/fixtures/earth_apollo17.jpg"; 50 let dst = "target/tmp/output.jpg"; 51 52 let source = PathBuf::from(src); 53 let dest = PathBuf::from(dst); 54 55 let mut input_file = std::fs::OpenOptions::new() 56 .read(true) 57 .open(&source) 58 .unwrap(); 59 60 let mut output_file = std::fs::OpenOptions::new() 61 .read(true) 62 .write(true) 63 .create(true) 64 .truncate(true) 65 .open(&dest) 66 .unwrap(); 67 68 let parent = Ingredient::from_file(source.as_path()).unwrap(); 69 70 // create an action assertion stating that we imported this file 71 let actions = Actions::new().add_action( 72 Action::new(c2pa_action::PLACED) 73 .set_parameter("identifier", parent.instance_id().to_owned()) 74 .unwrap(), 75 ); 76 77 // build a creative work assertion 78 let creative_work = CreativeWork::new() 79 .add_author(SchemaDotOrgPerson::new().set_name("me").unwrap()) 80 .unwrap(); 81 82 let exif = Exif::from_json_str( 83 r#"{ 84 "@context" : { 85 "exif": "http://ns.adobe.com/exif/1.0/" 86 }, 87 "exif:GPSVersionID": "2.2.0.0", 88 "exif:GPSLatitude": "39,21.102N", 89 "exif:GPSLongitude": "74,26.5737W", 90 "exif:GPSAltitudeRef": 0, 91 "exif:GPSAltitude": "100963/29890", 92 "exif:GPSTimeStamp": "2019-09-22T18:22:57Z" 93 }"#, 94 ) 95 .unwrap(); 96 97 // create a new Manifest 98 let mut manifest = Manifest::new(GENERATOR.to_owned()); 99 // add parent and assertions 100 manifest 101 .set_parent(parent) 102 .unwrap() 103 .add_assertion(&actions) 104 .unwrap() 105 .add_assertion(&creative_work) 106 .unwrap() 107 .add_assertion(&exif) 108 .unwrap(); 109 110 // get the composed manifest ready to insert into a file (returns manifest of same length as finished manifest) 111 let unfinished_manifest = manifest 112 .data_hash_placeholder(signer.reserve_size(), "jpg") 113 .unwrap(); 114 115 // Figure out where you want to put the manifest, let's put it at the beginning of the JPEG as first segment 116 // generate new file inserting unfinished manifest into file 117 input_file.rewind().unwrap(); 118 let mut before = vec![0u8; 2]; 119 input_file.read_exact(before.as_mut_slice()).unwrap(); 120 121 output_file.write_all(&before).unwrap(); 122 123 // write completed final manifest 124 output_file.write_all(&unfinished_manifest).unwrap(); 125 126 // write bytes after 127 let mut after_buf = Vec::new(); 128 input_file.read_to_end(&mut after_buf).unwrap(); 129 output_file.write_all(&after_buf).unwrap(); 130 131 // we need to add a data hash that excludes the manifest 132 let mut dh = DataHash::new("my_manifest", "sha265"); 133 let hr = HashRange::new(2, unfinished_manifest.len()); 134 dh.add_exclusion(hr); 135 136 // tell SDK to fill in the hash and sign to complete the manifest 137 output_file.rewind().unwrap(); 138 let final_manifest = manifest 139 .data_hash_embeddable_manifest(&dh, signer.as_ref(), "jpg", Some(&mut output_file)) 140 .unwrap(); 141 142 // replace temporary manifest with final signed manifest 143 // move to location where we inserted manifest, 144 // note: temporary manifest and final manifest will be the same size 145 output_file.seek(std::io::SeekFrom::Start(2)).unwrap(); 146 147 // write completed final manifest bytes over temporary bytes 148 output_file.write_all(&final_manifest).unwrap(); 149 150 // make sure the output file is correct 151 let manifest_store = ManifestStore::from_file(&dest).unwrap(); 152 153 // example of how to print out the whole manifest as json 154 println!("{manifest_store}\n"); 155 } 156 157 #[cfg(not(target_arch = "wasm32"))] 158 fn user_data_hash_with_user_hashing() { 159 const GENERATOR: &str = "test_app/0.1"; 160 161 // You will often implement your own Signer trait to perform on device signing 162 let signcert_path = "sdk/tests/fixtures/certs/es256.pub"; 163 let pkey_path = "sdk/tests/fixtures/certs/es256.pem"; 164 let signer = 165 create_signer::from_files(signcert_path, pkey_path, SigningAlg::Es256, None).unwrap(); 166 167 let src = "sdk/tests/fixtures/earth_apollo17.jpg"; 168 let dst = "target/tmp/output.jpg"; 169 170 let source = PathBuf::from(src); 171 let dest = PathBuf::from(dst); 172 173 let mut input_file = std::fs::OpenOptions::new() 174 .read(true) 175 .open(&source) 176 .unwrap(); 177 178 let mut output_file = std::fs::OpenOptions::new() 179 .read(true) 180 .write(true) 181 .create(true) 182 .truncate(true) 183 .open(&dest) 184 .unwrap(); 185 186 let parent = Ingredient::from_file(source.as_path()).unwrap(); 187 188 // create an action assertion stating that we imported this file 189 let actions = Actions::new().add_action( 190 Action::new(c2pa_action::PLACED) 191 .set_parameter("identifier", parent.instance_id().to_owned()) 192 .unwrap(), 193 ); 194 195 // build a creative work assertion 196 let creative_work = CreativeWork::new() 197 .add_author(SchemaDotOrgPerson::new().set_name("me").unwrap()) 198 .unwrap(); 199 200 let exif = Exif::from_json_str( 201 r#"{ 202 "@context" : { 203 "exif": "http://ns.adobe.com/exif/1.0/" 204 }, 205 "exif:GPSVersionID": "2.2.0.0", 206 "exif:GPSLatitude": "39,21.102N", 207 "exif:GPSLongitude": "74,26.5737W", 208 "exif:GPSAltitudeRef": 0, 209 "exif:GPSAltitude": "100963/29890", 210 "exif:GPSTimeStamp": "2019-09-22T18:22:57Z" 211 }"#, 212 ) 213 .unwrap(); 214 215 // create a new Manifest 216 let mut manifest = Manifest::new(GENERATOR.to_owned()); 217 // add parent and assertions 218 manifest 219 .set_parent(parent) 220 .unwrap() 221 .add_assertion(&actions) 222 .unwrap() 223 .add_assertion(&creative_work) 224 .unwrap() 225 .add_assertion(&exif) 226 .unwrap(); 227 228 // get the composed manifest ready to insert into a file (returns manifest of same length as finished manifest) 229 let unfinished_manifest = manifest 230 .data_hash_placeholder(signer.reserve_size(), "jpg") 231 .unwrap(); 232 233 // Figure out where you want to put the manifest, let's put it at the beginning of the JPEG as first segment 234 // we will need to add a data hash that excludes the manifest 235 let mut dh = DataHash::new("my_manifest", "sha265"); 236 let hr = HashRange::new(2, unfinished_manifest.len()); 237 dh.add_exclusion(hr); 238 239 // since the only thing we are excluding in this example is the manifest we can just hash all the bytes 240 // if you have additional exclusions you can add them to the DataHash and pass them to this function to be ' 241 // excluded from the hash generation 242 let hash = hash_stream_by_alg("sha256", &mut input_file, None, true).unwrap(); 243 dh.set_hash(hash); 244 245 // tell SDK to fill we will provide the hash and sign to complete the manifest 246 let final_manifest = manifest 247 .data_hash_embeddable_manifest(&dh, signer.as_ref(), "jpg", None) 248 .unwrap(); 249 250 // generate new file inserting final manifest into file 251 input_file.rewind().unwrap(); 252 let mut before = vec![0u8; 2]; 253 input_file.read_exact(before.as_mut_slice()).unwrap(); 254 255 output_file.write_all(&before).unwrap(); 256 257 // write completed final manifest 258 output_file.write_all(&final_manifest).unwrap(); 259 260 // write bytes after 261 let mut after_buf = Vec::new(); 262 input_file.read_to_end(&mut after_buf).unwrap(); 263 output_file.write_all(&after_buf).unwrap(); 264 265 // make sure the output file is correct 266 let manifest_store = ManifestStore::from_file(&dest).unwrap(); 267 268 // example of how to print out the whole manifest as json 269 println!("{manifest_store}\n"); 270 }