Live video on the AT Protocol
1use std::{io::Cursor, sync::Arc};
2
3use c2pa::{Builder, CallbackSigner, Reader, settings::Settings};
4use serde_json;
5
6#[derive(Debug, thiserror::Error, uniffi::Error)]
7#[uniffi(flat_error)]
8pub enum SPError {
9 #[error("No certificate chain found")]
10 NoCertificateChainFound,
11 #[error("C2PA error: {0}")]
12 C2paError(String),
13}
14
15#[uniffi::export]
16pub fn get_manifest_and_cert(data: Vec<u8>) -> Result<String, SPError> {
17 let reader = Reader::from_stream("video/mp4", Cursor::new(data))
18 .map_err(|e| SPError::C2paError(e.to_string()))?;
19 if let Some(manifest) = reader.active_manifest() {
20 let cert_chain = if let Some(si) = manifest.signature_info() {
21 si.cert_chain()
22 } else {
23 return Err(SPError::NoCertificateChainFound);
24 };
25
26 let result = serde_json::json!({
27 "manifest": manifest,
28 "cert": cert_chain
29 });
30
31 return Ok(result.to_string());
32 }
33 Err(SPError::NoCertificateChainFound)
34}
35
36#[uniffi::export(with_foreign)]
37pub trait GoSigner: Send + Sync {
38 fn sign(&self, data: Vec<u8>) -> Result<Vec<u8>, SPError>;
39}
40
41// #[derive(uniffi::Object)]
42// struct Authenticator {
43// gosigner: Arc<dyn GoSigner>,
44// }
45
46// impl Authenticator {
47// pub fn new(gosigner: Arc<dyn GoSigner>) -> Self {
48// Self { gosigner }
49// }
50
51// pub fn login(&self) {
52// let username = self.gosigner.get("username".into());
53// let password = self.gosigner.get("password".into());
54// }
55// }
56
57const TOML_SETTINGS: &str = r#"
58version_major = 1
59version_minor = 0
60
61[trust]
62
63[core]
64debug = true
65hash_alg = "sha256"
66salt_jumbf_boxes = true
67prefer_box_hash = false
68merkle_tree_max_proofs = 5
69compress_manifests = true
70
71[verify]
72verify_after_reading = false
73verify_after_sign = false
74verify_trust = false
75verify_timestamp_trust = false
76ocsp_fetch = false
77remote_manifest_fetch = false
78check_ingredient_trust = false
79skip_ingredient_conflict_resolution = false
80strict_v1_validation = false
81
82[builder.thumbnail]
83enabled = false
84ignore_errors = true
85long_edge = 1024
86prefer_smallest_format = true
87quality = "medium"
88
89[builder.actions]
90all_actions_included = false
91
92[builder.actions.auto_created_action]
93enabled = true
94source_type = "http://c2pa.org/digitalsourcetype/empty"
95
96[builder.actions.auto_opened_action]
97enabled = true
98
99[builder.actions.auto_placed_action]
100enabled = true
101"#;
102
103#[uniffi::export]
104pub fn sign(
105 manifest: String,
106 data: Vec<u8>,
107 certs: Vec<u8>,
108 gosigner: Arc<dyn GoSigner>,
109) -> Result<Vec<u8>, SPError> {
110 Settings::from_toml(TOML_SETTINGS).map_err(|e| SPError::C2paError(e.to_string()))?;
111 let callback_signer = CallbackSigner::new(
112 move |_context: *const (), data: &[u8]| {
113 gosigner
114 .sign(data.to_vec())
115 .map_err(|e| c2pa::Error::BadParam(e.to_string()))
116 },
117 c2pa::SigningAlg::Es256K,
118 certs,
119 );
120 let mut builder =
121 Builder::from_json(&manifest).map_err(|e| SPError::C2paError(e.to_string()))?;
122 let mut output = Vec::new();
123 let mut input_cursor = Cursor::new(data);
124 let mut output_cursor = Cursor::new(&mut output);
125 builder
126 .sign(
127 &callback_signer,
128 "video/mp4",
129 &mut input_cursor,
130 &mut output_cursor,
131 )
132 .map_err(|e| SPError::C2paError(e.to_string()))?;
133 Ok(output)
134}