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

v2api.rs (6505B)


      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 //! Example App showing how to use the new v2 API
     15 use std::io::{Cursor, Seek};
     16 
     17 use anyhow::Result;
     18 use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader, SigningAlg};
     19 use serde_json::json;
     20 
     21 const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg");
     22 const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub");
     23 const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem");
     24 
     25 fn manifest_def(title: &str, format: &str) -> String {
     26     json!({
     27         "title": title,
     28         "format": format,
     29         "claim_generator_info": [
     30             {
     31                 "name": "c2pa test",
     32                 "version": env!("CARGO_PKG_VERSION")
     33             }
     34         ],
     35         "thumbnail": {
     36             "format": format,
     37             "identifier": "manifest_thumbnail.jpg"
     38         },
     39         "ingredients": [
     40             {
     41                 "title": "Test",
     42                 "format": "image/jpeg",
     43                 "instance_id": "12345",
     44                 "relationship": "inputTo"
     45             }
     46         ],
     47         "assertions": [
     48             {
     49                 "label": "c2pa.actions",
     50                 "data": {
     51                     "actions": [
     52                         {
     53                             "action": "c2pa.edited",
     54                             "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
     55                             "softwareAgent": {
     56                                 "name": "My AI Tool",
     57                                 "version": "0.1.0"
     58                             }
     59                         }
     60                     ]
     61                 }
     62             }
     63         ]
     64     }).to_string()
     65 }
     66 
     67 /// This example demonstrates how to use the new v2 API to create a manifest store
     68 /// It uses only streaming apis, showing how to avoid file i/o
     69 /// This example uses the `ed25519` signing algorithm
     70 fn main() -> Result<()> {
     71     let title = "v2_edited.jpg";
     72     let format = "image/jpeg";
     73     let parent_name = "CA.jpg";
     74     let mut source = Cursor::new(TEST_IMAGE);
     75 
     76     let modified_core = json!({
     77         "core": {
     78             "debug": true,
     79             "hash_alg": "sha512",
     80             "max_memory_usage": 123456
     81         }
     82     })
     83     .to_string();
     84 
     85     load_settings_from_str(&modified_core, "json")?;
     86 
     87     let json = manifest_def(title, format);
     88 
     89     let mut builder = Builder::from_json(&json)?;
     90     builder.add_ingredient(
     91         json!({
     92             "title": parent_name,
     93             "relationship": "parentOf"
     94         })
     95         .to_string(),
     96         format,
     97         &mut source,
     98     )?;
     99 
    100     let thumb_uri = builder
    101         .definition
    102         .thumbnail
    103         .as_ref()
    104         .map(|t| t.identifier.clone());
    105 
    106     // add a manifest thumbnail ( just reuse the image for now )
    107     if let Some(uri) = thumb_uri {
    108         if !uri.starts_with("self#jumbf") {
    109             source.rewind()?;
    110             builder.add_resource(&uri, &mut source)?;
    111         }
    112     }
    113 
    114     // write the manifest builder to a zipped stream
    115     let mut zipped = Cursor::new(Vec::new());
    116     builder.to_archive(&mut zipped)?;
    117 
    118     // write the zipped stream to a file for debugging
    119     //let debug_path = format!("{}/../target/test.zip", env!("CARGO_MANIFEST_DIR"));
    120     // std::fs::write(debug_path, zipped.get_ref())?;
    121 
    122     // unzip the manifest builder from the zipped stream
    123     zipped.rewind()?;
    124 
    125     let ed_signer = |_context: *const (), data: &[u8]| ed_sign(data, PRIVATE_KEY);
    126     let signer = CallbackSigner::new(ed_signer, SigningAlg::Ed25519, CERTS);
    127 
    128     let mut builder = Builder::from_archive(&mut zipped)?;
    129     // sign the ManifestStoreBuilder and write it to the output stream
    130     let mut dest = Cursor::new(Vec::new());
    131     builder.sign(&signer, format, &mut source, &mut dest)?;
    132 
    133     // read and validate the signed manifest store
    134     dest.rewind()?;
    135 
    136     let reader = Reader::from_stream(format, &mut dest)?;
    137 
    138     // extract a thumbnail image from the ManifestStore
    139     let mut thumbnail = Cursor::new(Vec::new());
    140     if let Some(manifest) = reader.active_manifest() {
    141         if let Some(thumbnail_ref) = manifest.thumbnail_ref() {
    142             reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?;
    143             println!(
    144                 "wrote thumbnail {} of size {}",
    145                 thumbnail_ref.format,
    146                 thumbnail.get_ref().len()
    147             );
    148         }
    149     }
    150 
    151     println!("{}", reader.json());
    152     assert!(reader.validation_status().is_none());
    153     assert_eq!(reader.active_manifest().unwrap().title().unwrap(), title);
    154 
    155     Ok(())
    156 }
    157 
    158 // Sign the data using the Ed25519 algorithm
    159 fn ed_sign(data: &[u8], private_key: &[u8]) -> c2pa::Result<Vec<u8>> {
    160     use ed25519_dalek::{Signature, Signer, SigningKey};
    161     use pem::parse;
    162 
    163     // Parse the PEM data to get the private key
    164     let pem = parse(private_key).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?;
    165     // For Ed25519, the key is 32 bytes long, so we skip the first 16 bytes of the PEM data
    166     let key_bytes = &pem.contents()[16..];
    167     let signing_key =
    168         SigningKey::try_from(key_bytes).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?;
    169     // Sign the data
    170     let signature: Signature = signing_key.sign(data);
    171 
    172     Ok(signature.to_bytes().to_vec())
    173 }
    174 
    175 // #[cfg(feature = "openssl")]
    176 // use openssl::{error::ErrorStack, pkey::PKey};
    177 // #[cfg(feature = "openssl")]
    178 // fn ed_sign(data: &[u8], pkey: &[u8]) -> std::result::Result<Vec<u8>, ErrorStack> {
    179 //     let pkey = PKey::private_key_from_pem(pkey)?;
    180 //     let mut signer = openssl::sign::Signer::new_without_digest(&pkey)?;
    181 //     signer.sign_oneshot_to_vec(data)
    182 // }
    183 
    184 #[cfg(test)]
    185 mod tests {
    186     #[cfg(target_arch = "wasm32")]
    187     use wasm_bindgen_test::*;
    188 
    189     use super::*;
    190 
    191     #[cfg_attr(not(target_arch = "wasm32"), actix::test)]
    192     #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
    193     async fn test_v2_api() -> Result<()> {
    194         main()
    195     }
    196 }