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 }