commit c939223b2e0d4999a0f3d31c6205dc7e9294d99a
parent 47d4afc16a6c5b90b8aac874f1f6ce39003ac077
Author: Darren Clarke <darren@redaranj.com>
Date: Wed, 6 Dec 2023 09:17:10 +0100
Add certificate convenience functions, examples
Diffstat:
9 files changed, 159 insertions(+), 129 deletions(-)
diff --git a/Makefile.toml b/Makefile.toml
@@ -74,7 +74,10 @@ cross build --target aarch64-linux-android --release
'''
[tasks.android-build-bindings]
-script = 'cargo run --bin uniffi-bindgen generate --language kotlin --library ./target/x86_64-linux-android/release/libsimple_c2pa.so --out-dir ${BINDINGS_DIR}/kotlin'
+script = '''
+cargo build --target x86_64-linux-android
+cargo run --bin uniffi-bindgen generate --language kotlin --library ./target/x86_64-linux-android/debug/libsimple_c2pa.so --out-dir ${BINDINGS_DIR}/kotlin
+'''
[tasks.android-assemble-aar-files]
env = { NAME = "simple_c2pa", LIB_NAME = "lib${NAME}.so", OUT_DIR = "out/android", JNILIBS_DIR = "${OUT_DIR}/jniLibs", ANDROID_BINDINGS_DIR = "kotlin/info/guardianproject/simple_c2pa" }
diff --git a/src/assertions.rs b/src/assertions.rs
@@ -3,9 +3,8 @@ use crate::content_credentials::ContentCredentials;
use c2pa::assertions::{c2pa_action, labels, Action, Actions, Exif, SchemaDotOrg};
use serde::{Deserialize, Serialize};
use std::result::Result;
-use std::sync::Arc;
-#[derive(uniffi::Object)]
+#[derive(uniffi::Record)]
pub struct ExifData {
pub gps_version_id: Option<String>,
pub latitude: Option<String>,
@@ -31,37 +30,6 @@ pub struct ExifData {
pub lens_specification: Option<Vec<f64>>,
}
-#[uniffi::export]
-impl ExifData {
- #[uniffi::constructor]
- pub fn new() -> Arc<Self> {
- Arc::new(ExifData {
- gps_version_id: None,
- latitude: None,
- longitude: None,
- altitude_ref: None,
- altitude: None,
- timestamp: None,
- speed_ref: None,
- speed: None,
- direction_ref: None,
- direction: None,
- destination_bearing_ref: None,
- destination_bearing: None,
- positioning_error: None,
- exposure_time: None,
- f_number: None,
- color_space: None,
- digital_zoom_ratio: None,
- make: None,
- model: None,
- lens_make: None,
- lens_model: None,
- lens_specification: None,
- })
- }
-}
-
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
struct AIDataMiningUsageJSON {
@@ -195,7 +163,7 @@ impl ContentCredentials {
Ok(())
}
- pub fn add_exif_assertion(&self, exif_data: Arc<ExifData>) -> Result<(), SimpleC2PAError> {
+ pub fn add_exif_assertion(&self, exif_data: ExifData) -> Result<(), SimpleC2PAError> {
let mut exif = Exif::new();
if let Some(gps_version_id) = exif_data.gps_version_id.clone() {
exif = exif.insert("exif:GPSVersionID", gps_version_id)?;
@@ -309,3 +277,18 @@ impl ContentCredentials {
Ok(())
}
}
+/*
+#[cfg(test)]
+pub mod tests {
+ #[test]
+ fn get_creative_work_assertion() {
+ let work = crate::assertions::get_creative_work_assertion(
+ "John Doe".to_owned(),
+ "johndoe".to_owned(),
+ "https://instagram.com".to_owned(),
+ )
+ .unwrap();
+ assert_eq!("X", "CreativeWork");
+ }
+}
+*/
diff --git a/src/certificates.rs b/src/certificates.rs
@@ -136,9 +136,9 @@ impl CertificateOptions {
#[derive(Clone, Debug, uniffi::Object)]
pub struct Certificate {
- pub certificate_data: Arc<FileData>,
- pub private_key_data: Arc<FileData>,
- pub parent_certificate: Option<Arc<Certificate>>,
+ certificate_data: Arc<FileData>,
+ private_key_data: Arc<FileData>,
+ parent_certificate: Option<Arc<Certificate>>,
}
#[uniffi::export]
@@ -155,6 +155,14 @@ impl Certificate {
parent_certificate,
})
}
+
+ pub fn get_certificate_bytes(&self) -> Result<Vec<u8>, SimpleC2PAError> {
+ return self.certificate_data.get_bytes();
+ }
+
+ pub fn get_private_key_bytes(&self) -> Result<Vec<u8>, SimpleC2PAError> {
+ return self.private_key_data.get_bytes();
+ }
}
fn generate_serial_number() -> Result<Asn1Integer, SimpleC2PAError> {
let random = ring::rand::SystemRandom::new();
@@ -261,6 +269,47 @@ pub fn create_certificate(
}
#[uniffi::export]
+pub fn create_root_certificate(
+ organization: Option<String>,
+ validity_days: Option<u32>,
+) -> Result<Arc<Certificate>, SimpleC2PAError> {
+ let key = create_private_key().unwrap();
+ let options = CertificateOptions::new(
+ key.clone(),
+ CertificateType::OfflineRoot {
+ organization,
+ validity_days,
+ },
+ None,
+ None,
+ None,
+ );
+ let certificate = create_certificate(options)?;
+ Ok(certificate)
+}
+
+#[uniffi::export]
+pub fn create_content_credentials_certificate(
+ root_certificate: Option<Arc<Certificate>>,
+ organization: Option<String>,
+ validity_days: Option<u32>,
+) -> Result<Arc<Certificate>, SimpleC2PAError> {
+ let key = create_private_key().unwrap();
+ let options = CertificateOptions::new(
+ key.clone(),
+ CertificateType::ContentCredentials {
+ organization,
+ validity_days,
+ },
+ root_certificate,
+ None,
+ None,
+ );
+ let certificate = create_certificate(options)?;
+ Ok(certificate)
+}
+
+#[uniffi::export]
pub fn request_signed_certificate(
_options: Arc<CertificateOptions>,
) -> Result<String, SimpleC2PAError> {
diff --git a/src/content_credentials.rs b/src/content_credentials.rs
@@ -80,8 +80,8 @@ impl ContentCredentials {
certificate: Arc<Certificate>,
output_file: Arc<FileData>,
) -> Result<(), SimpleC2PAError> {
- let cert = certificate.certificate_data.get_bytes()?;
- let pkey = certificate.private_key_data.get_bytes()?;
+ let cert = certificate.get_certificate_bytes()?;
+ let pkey = certificate.get_private_key_bytes()?;
let algorithms = vec![
SigningAlg::Es256,
SigningAlg::Es384,
diff --git a/src/lib.rs b/src/lib.rs
@@ -5,8 +5,8 @@ pub use common::FileData;
mod certificates;
pub use certificates::{
- create_certificate, create_private_key, request_signed_certificate, CertificateOptions,
- CertificateType,
+ create_certificate, create_content_credentials_certificate, create_private_key,
+ create_root_certificate, request_signed_certificate, CertificateOptions, CertificateType,
};
mod content_credentials;
diff --git a/tests/examples.rs b/tests/examples.rs
@@ -0,0 +1,82 @@
+#[cfg(test)]
+pub mod tests {
+ use simple_c2pa::{
+ create_content_credentials_certificate, create_root_certificate, ApplicationInfo,
+ ContentCredentials, ExifData, FileData,
+ };
+ use std::fs;
+
+ #[test]
+ fn basic_example() {
+ let image_path = "tests/media/test-1.jpg";
+ let file_name = image_path.split("/").last().unwrap().to_string();
+ let file_data = fs::read(image_path).expect("Can't read image");
+
+ let root_certificate = create_root_certificate(None, None).unwrap();
+ let content_credentials_certificate =
+ create_content_credentials_certificate(Some(root_certificate.clone()), None, None)
+ .unwrap();
+
+ let file = FileData::new(None, Some(file_data), Some(file_name.clone()));
+ let cc = ContentCredentials::new(content_credentials_certificate, file, None);
+ cc.add_created_assertion().unwrap();
+
+ let output_path = format!("outputs/c2pa-basic-{}", file_name);
+ let file_data = cc.embed_manifest(Some(output_path.clone())).unwrap();
+ fs::write(output_path, file_data.get_bytes().unwrap()).expect("Can't write file");
+ }
+
+ #[test]
+ fn complex_example() {
+ let image_path = "tests/media/test-1.jpg";
+ let file_name = image_path.split("/").last().unwrap().to_string();
+ let file_data = fs::read(image_path).expect("Can't read image");
+ let fingerprint = "BA08 71E8 0200 B95D 8297 7ED0 4D1E C37F 88A7 FDCE".to_string();
+
+ let root_certificate = create_root_certificate(None, None).unwrap();
+ let content_credentials_certificate =
+ create_content_credentials_certificate(Some(root_certificate.clone()), None, None)
+ .unwrap();
+
+ let file = FileData::new(None, Some(file_data), Some(file_name.clone()));
+ let app_info = ApplicationInfo::new("SampleApp".to_string(), "1.0.0".to_string(), None);
+ let cc = ContentCredentials::new(content_credentials_certificate, file, Some(app_info));
+ println!("{:?}", cc);
+ cc.add_created_assertion().unwrap();
+
+ let exif_data = ExifData {
+ gps_version_id: Some("2.2.0.0".to_string()),
+ latitude: Some("39,21.102N".to_string()),
+ longitude: Some("74,26.5737W".to_string()),
+ altitude_ref: Some(0),
+ altitude: Some("100963/29890".to_string()),
+ timestamp: Some("2019-09-22T18:22:57Z".to_string()),
+ speed_ref: Some("K".to_string()),
+ speed: Some("4009/161323".to_string()),
+ direction_ref: Some("T".to_string()),
+ direction: Some("296140/911".to_string()),
+ destination_bearing_ref: Some("T".to_string()),
+ destination_bearing: Some("296140/911".to_string()),
+ positioning_error: Some("13244/2207".to_string()),
+ exposure_time: Some("1/100".to_string()),
+ f_number: Some(4.0),
+ color_space: Some(1),
+ digital_zoom_ratio: Some(2.0),
+ make: Some("ProofMode".to_string()),
+ model: Some("ProofMode In-App Camera v2.0".to_string()),
+ lens_make: Some("CameraCompany".to_string()),
+ lens_model: Some("17.0-35.0 mm".to_string()),
+ lens_specification: Some(vec![1.55, 4.2, 1.6, 2.4]),
+ };
+ cc.add_exif_assertion(exif_data).unwrap();
+ cc.add_instagram_assertion("johndoe".to_string(), "John Doe".to_string())
+ .unwrap();
+ cc.add_pgp_assertion(fingerprint, "88A7FDCE".to_string())
+ .unwrap();
+ cc.add_website_assertion("https://redaranj.com".to_string())
+ .unwrap();
+ let output_path = format!("outputs/c2pa-complex-{}", file_name);
+ let file_data = cc.embed_manifest(Some(output_path.clone())).unwrap();
+ fs::write(output_path, file_data.get_bytes().unwrap()).expect("Can't write file");
+ }
+}
diff --git a/tests/main.rs b/tests/main.rs
@@ -1,87 +0,0 @@
-use simple_c2pa::{create_certificate, create_private_key, CertificateOptions, CertificateType};
-use simple_c2pa::{ApplicationInfo, ContentCredentials, ExifData, FileData};
-use std::env;
-use std::fs;
-
-fn main() {
- let args: Vec<String> = env::args().collect();
-
- if args.len() != 2 {
- eprintln!("Usage: command image.jpg data.json");
- std::process::exit(1);
- }
-
- let image_path = &args[1];
- let file_name = image_path.split("/").last().unwrap().to_string();
- let file_data = fs::read(image_path).expect("Can't read image");
- let fingerprint = "BA08 71E8 0200 B95D 8297 7ED0 4D1E C37F 88A7 FDCE".to_string();
-
- let root_key = create_private_key().unwrap();
- let root_options = CertificateOptions::new(
- root_key.clone(),
- CertificateType::OfflineRoot {
- organization: None,
- validity_days: None,
- },
- None,
- None,
- None,
- );
- let root_cert = create_certificate(root_options).unwrap();
-
- let content_credentials_key = create_private_key().unwrap();
- let content_credentials_options = CertificateOptions::new(
- content_credentials_key.clone(),
- CertificateType::ContentCredentials {
- organization: None,
- validity_days: None,
- },
- Some(root_cert.clone()),
- None,
- None,
- );
- let content_credentials_certificate = create_certificate(content_credentials_options).unwrap();
-
- let certificate = content_credentials_certificate.clone();
- let file = FileData::new(None, Some(file_data), Some(file_name.clone()));
- let app_info = ApplicationInfo::new("SampleApp".to_string(), "1.0.0".to_string(), None);
- let cc = ContentCredentials::new(certificate, file, Some(app_info));
- println!("{:?}", cc);
- cc.add_created_assertion().unwrap();
- /*
- let exif_data = ExifData::new();
- exif_data.gps_version_id = Some("2.2.0.0".to_string());
- exif_data.latitude = Some("39,21.102N".to_string());
- exif_data.longitude = Some("74,26.5737W".to_string());
- exif_data.altitude_ref = Some(0);
- exif_data.altitude = Some("100963/29890".to_string());
- exif_data.timestamp = Some("2019-09-22T18:22:57Z".to_string());
- exif_data.speed_ref = Some("K".to_string());
- exif_data.speed = Some("4009/161323".to_string());
- exif_data.direction_ref = Some("T".to_string());
- exif_data.direction = Some("296140/911".to_string());
- exif_data.destination_bearing_ref = Some("T".to_string());
- exif_data.destination_bearing = Some("296140/911".to_string());
- exif_data.positioning_error = Some("13244/2207".to_string());
- exif_data.exposure_time = Some("1/100".to_string());
- exif_data.f_number = Some(4.0);
- exif_data.color_space = Some(1);
- exif_data.digital_zoom_ratio = Some(2.0);
- exif_data.make = Some("ProofMode".to_string());
- exif_data.model = Some("ProofMode In-App Camera v2.0".to_string());
- exif_data.lens_make = Some("CameraCompany".to_string());
- exif_data.lens_model = Some("17.0-35.0 mm".to_string());
- exif_data.lens_specification = Some(vec![1.55, 4.2, 1.6, 2.4]);
-
- cc.add_exif_assertion(exif_data).unwrap();
- */
- cc.add_instagram_assertion("darrenjclarke".to_string(), "Darren Clarke".to_string())
- .unwrap();
- cc.add_pgp_assertion(fingerprint, "88A7FDCE".to_string())
- .unwrap();
- cc.add_website_assertion("https://redaranj.com".to_string())
- .unwrap();
- let output_path = format!("outputs/c2pa-{}", file_name);
- let file_data = cc.embed_manifest(Some(output_path.clone())).unwrap();
- fs::write(output_path, file_data.get_bytes().unwrap()).expect("Can't write file");
-}
diff --git a/tests/media/test-1.jpg b/tests/media/test-1.jpg
Binary files differ.
diff --git a/tests/media/test-1.png b/tests/media/test-1.png
Binary files differ.