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

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 }