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

v2_api_integration.rs (5608B)


      1 // Copyright 2024 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 /// Complete functional integration test with acquisitions and ingredients.
     15 //  Isolate from wasm by wrapping in module.
     16 #[cfg(not(target_arch = "wasm32"))] // wasm doesn't support ed25519 yet
     17 mod integration_v2 {
     18 
     19     use std::io::{Cursor, Seek};
     20 
     21     use anyhow::Result;
     22     use c2pa::{Builder, CallbackSigner, Reader, SigningAlg};
     23     use serde_json::json;
     24 
     25     const PARENT_JSON: &str = r#"
     26     {
     27         "title": "Parent Test",
     28         "format": "image/jpeg",
     29         "relationship": "parentOf"
     30     }
     31     "#;
     32 
     33     const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg");
     34     const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub");
     35     const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem");
     36 
     37     fn get_manifest_def(title: &str, format: &str) -> String {
     38         json!({
     39         "title": title,
     40         "format": format,
     41         "claim_generator_info": [
     42             {
     43                 "name": "c2pa test",
     44                 "version": env!("CARGO_PKG_VERSION")
     45             }
     46         ],
     47         "thumbnail": {
     48             "format": "image/jpeg",
     49             "identifier": "manifest_thumbnail.jpg"
     50         },
     51         "ingredients": [
     52             {
     53                 "title": "Test",
     54                 "format": "image/jpeg",
     55                 "instance_id": "12345",
     56                 "relationship": "inputTo"
     57             }
     58         ],
     59         "assertions": [
     60             {
     61                 "label": "c2pa.actions",
     62                 "data": {
     63                     "actions": [
     64                         {
     65                             "action": "c2pa.edited",
     66                             "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
     67                             "softwareAgent": "Adobe Firefly 0.1.0"
     68                         }
     69                     ]
     70                 }
     71             }
     72         ]
     73     }).to_string()
     74     }
     75 
     76     #[test]
     77     fn test_v2_integration() -> Result<()> {
     78         let title = "CA.jpg";
     79         let format = "image/jpeg";
     80         let mut source = Cursor::new(TEST_IMAGE);
     81 
     82         let json = get_manifest_def(title, format);
     83 
     84         // don't try to verify on wasm since it doesn't support ed25519 yet
     85 
     86         let mut builder = Builder::from_json(&json)?;
     87         builder.add_ingredient(PARENT_JSON, format, &mut source)?;
     88 
     89         // add a manifest thumbnail ( just reuse the image for now )
     90         source.rewind()?;
     91         builder.add_resource("manifest_thumbnail.jpg", &mut source)?;
     92 
     93         // write the manifest builder to a zipped stream
     94         let mut zipped = Cursor::new(Vec::new());
     95         builder.to_archive(&mut zipped)?;
     96 
     97         // write the zipped stream to a file for debugging
     98         //let debug_path = format!("{}/../target/test.zip", env!("CARGO_MANIFEST_DIR"));
     99         // std::fs::write(debug_path, zipped.get_ref())?;
    100 
    101         // unzip the manifest builder from the zipped stream
    102         zipped.rewind()?;
    103 
    104         let mut dest = {
    105             let ed_signer = |_context: *const _, data: &[u8]| ed_sign(data, PRIVATE_KEY);
    106             let signer = CallbackSigner::new(ed_signer, SigningAlg::Ed25519, CERTS);
    107             let mut builder = Builder::from_archive(&mut zipped)?;
    108             // sign the ManifestStoreBuilder and write it to the output stream
    109             let mut dest = Cursor::new(Vec::new());
    110             builder.sign(&signer, format, &mut source, &mut dest)?;
    111 
    112             // read and validate the signed manifest store
    113             dest.rewind()?;
    114             dest
    115         };
    116 
    117         let reader = Reader::from_stream(format, &mut dest)?;
    118 
    119         // extract a thumbnail image from the ManifestStore
    120         let mut thumbnail = Cursor::new(Vec::new());
    121         if let Some(manifest) = reader.active_manifest() {
    122             if let Some(thumbnail_ref) = manifest.thumbnail_ref() {
    123                 reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?;
    124                 println!(
    125                     "wrote thumbnail {} of size {}",
    126                     thumbnail_ref.format,
    127                     thumbnail.get_ref().len()
    128                 );
    129             }
    130         }
    131 
    132         println!("{}", reader.json());
    133         assert!(reader.validation_status().is_none());
    134         assert_eq!(reader.active_manifest().unwrap().title().unwrap(), title);
    135 
    136         Ok(())
    137     }
    138 
    139     fn ed_sign(data: &[u8], private_key: &[u8]) -> c2pa::Result<Vec<u8>> {
    140         use ed25519_dalek::{Signature, Signer, SigningKey};
    141         use pem::parse;
    142 
    143         // Parse the PEM data to get the private key
    144         let pem = parse(private_key).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?;
    145         // For Ed25519, the key is 32 bytes long, so we skip the first 16 bytes of the PEM data
    146         let key_bytes = &pem.contents()[16..];
    147         let signing_key =
    148             SigningKey::try_from(key_bytes).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?;
    149         // Sign the data
    150         let signature: Signature = signing_key.sign(data);
    151 
    152         Ok(signature.to_bytes().to_vec())
    153     }
    154 }