Skip to content

Commit 74afb3f

Browse files
committed
Add Callback signer and ingredient support
remove v1 apis build with minimal c2pa-rs features (only openssl)
1 parent 4c651af commit 74afb3f

File tree

11 files changed

+399
-126
lines changed

11 files changed

+399
-126
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ authors = ["Gavin Peacock <[email protected]"]
99
name = "c2pa"
1010
crate-type = ["cdylib"]
1111

12+
[features]
13+
v1 = ["c2pa/file_io"]
14+
1215
[dependencies]
13-
c2pa = {path="../c2pa-rs/sdk", features = ["file_io", "openssl_sign"]}
16+
c2pa = {git = "https://github.com/contentauth/c2pa-rs.git", branch = "gpeacock/manifest_store_builder", features = ["openssl", "openssl_sign"]}
1417
pem = "3.0.3"
1518
serde = { version = "1.0.197", features = ["derive"] }
1619
serde_derive = "1.0"

src/c2pa.udl

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
namespace c2pa {
33
string version();
44
string sdk_version();
5-
[Throws=Error]
6-
string read_file([ByRef] string path, string? data_dir);
7-
[Throws=Error]
8-
string read_ingredient_file([ByRef] string path, [ByRef] string data_dir);
9-
[Throws=Error]
10-
sequence<u8> sign_file([ByRef] string source, [ByRef] string dest, [ByRef] string manifest, [ByRef] SignerInfo signer_info, string? data_dir);
5+
/// [Throws=Error]
6+
/// string read_file([ByRef] string path, string? data_dir);
7+
/// [Throws=Error]
8+
/// string read_ingredient_file([ByRef] string path, [ByRef] string data_dir);
9+
/// [Throws=Error]
10+
/// sequence<u8> sign_file([ByRef] string source, [ByRef] string dest, [ByRef] string manifest, [ByRef] SignerInfo signer_info, string? data_dir);
1111
};
1212

1313
[Error]
@@ -71,12 +71,12 @@ interface Reader {
7171
u64 resource([ByRef] string uri, [ByRef] Stream stream);
7272
};
7373

74-
dictionary SignerInfo {
75-
string alg;
76-
sequence<u8> sign_cert;
77-
sequence<u8> private_key;
78-
string? ta_url;
79-
};
74+
///dictionary SignerInfo {
75+
/// string alg;
76+
/// sequence<u8> sign_cert;
77+
/// sequence<u8> private_key;
78+
/// string? ta_url;
79+
///};
8080

8181
dictionary SignerConfig {
8282
SigningAlg alg;
@@ -103,6 +103,9 @@ interface Builder {
103103
[Throws=Error]
104104
void add_resource([ByRef] string uri, [ByRef] Stream stream );
105105

106+
[Throws=Error]
107+
void add_ingredient([ByRef] string ingredient_json, [ByRef] string format, [ByRef] Stream stream );
108+
106109
[Throws=Error]
107110
bytes sign([ByRef] string format, [ByRef] Stream input, [ByRef] Stream output ,[ByRef] CallbackSigner signer);
108111
};

src/callback_signer.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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+
// Unless required by applicable law or agreed to in writing,
7+
// this software is distributed on an "AS IS" BASIS, WITHOUT
8+
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
9+
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
10+
// specific language governing permissions and limitations under
11+
// each license.
12+
13+
use c2pa::{Signer, SigningAlg};
14+
15+
use crate::Result;
16+
17+
18+
/// Defines the callback interface for a signer
19+
pub trait SignerCallback: Send + Sync {
20+
/// Read a stream of bytes from the stream
21+
fn sign(&self, bytes: Vec<u8>) -> Result<Vec<u8>>;
22+
}
23+
24+
/// Configuration for a Signer
25+
#[repr(C)]
26+
pub struct SignerConfig {
27+
/// Returns the algorithm of the Signer.
28+
pub alg: c2pa::SigningAlg,
29+
30+
/// Returns the certificates as a Vec containing a Vec of DER bytes for each certificate.
31+
pub certs: Vec<u8>,
32+
33+
/// URL for time authority to time stamp the signature
34+
pub time_authority_url: Option<String>,
35+
36+
/// Try to fetch OCSP response for the signing cert if available
37+
pub use_ocsp: bool,
38+
}
39+
40+
/// Callback signer that uses a callback to sign data
41+
pub struct CallbackSigner {
42+
callback: Box<dyn SignerCallback>,
43+
alg: SigningAlg,
44+
sign_certs: Vec<u8>,
45+
ta_url: Option<String>,
46+
}
47+
48+
impl CallbackSigner {
49+
pub fn new(
50+
callback: Box<dyn SignerCallback>,
51+
config: SignerConfig,
52+
// alg: SigningAlg,
53+
// sign_certs: Vec<u8>,
54+
// ta_url: Option<String>,
55+
) -> Self {
56+
Self {
57+
callback,
58+
alg: config.alg,
59+
sign_certs: config.certs,
60+
ta_url: config.time_authority_url,
61+
}
62+
}
63+
}
64+
65+
impl Signer for CallbackSigner {
66+
fn sign(&self, data: &[u8]) -> c2pa::Result<Vec<u8>> {
67+
self.callback
68+
.sign(data.to_vec())
69+
.map_err(|e| c2pa::Error::BadParam(e.to_string()))
70+
}
71+
72+
fn alg(&self) -> SigningAlg {
73+
self.alg
74+
}
75+
76+
fn certs(&self) -> c2pa::Result<Vec<Vec<u8>>> {
77+
let mut pems =
78+
pem::parse_many(&self.sign_certs).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?;
79+
Ok(pems.drain(..).map(|p| p.into_contents()).collect())
80+
}
81+
82+
fn reserve_size(&self) -> usize {
83+
20000
84+
}
85+
86+
fn time_authority_url(&self) -> Option<String> {
87+
self.ta_url.clone()
88+
}
89+
}

src/json_api.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,6 @@ use c2pa::{Ingredient, Manifest, ManifestStore};
1414

1515
use crate::{Error, Result, SignerInfo};
1616

17-
/// Returns the version of the C2PA library
18-
pub fn sdk_version() -> String {
19-
format!(
20-
"{}/{} {}/{}",
21-
env!("CARGO_PKG_NAME"),
22-
env!("CARGO_PKG_VERSION"),
23-
c2pa::NAME,
24-
c2pa::VERSION
25-
)
26-
}
27-
2817
/// Returns ManifestStore JSON string from a file path.
2918
///
3019
/// If data_dir is provided, any thumbnail or c2pa data will be written to that folder.

src/lib.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@ pub use c2pa::SigningAlg;
1919
/// these all need to be public so that the uniffi macro can see them
2020
mod error;
2121
pub use error::{Error, Result};
22+
#[cfg(feature = "v1")]
2223
mod json_api;
23-
pub use json_api::{read_file, read_ingredient_file, sdk_version, sign_file};
24+
#[cfg(feature = "v1")]
25+
pub use json_api::{read_file, read_ingredient_file, sign_file};
26+
#[cfg(feature = "v1")]
2427
mod signer_info;
28+
#[cfg(feature = "v1")]
2529
pub use signer_info::{CallbackSigner, SignerCallback, SignerConfig, SignerInfo};
30+
mod callback_signer;
31+
pub use callback_signer::{CallbackSigner, SignerCallback, SignerConfig};
2632
mod streams;
2733
pub use streams::{SeekMode, Stream, StreamAdapter};
2834

@@ -37,6 +43,18 @@ fn version() -> String {
3743
String::from(env!("CARGO_PKG_VERSION"))
3844
}
3945

46+
47+
/// Returns the version of the C2PA library
48+
pub fn sdk_version() -> String {
49+
format!(
50+
"{}/{} {}/{}",
51+
env!("CARGO_PKG_NAME"),
52+
env!("CARGO_PKG_VERSION"),
53+
c2pa::NAME,
54+
c2pa::VERSION
55+
)
56+
}
57+
4058
pub struct Reader {
4159
reader: RwLock<c2pa::Reader>,
4260
}
@@ -117,6 +135,16 @@ impl Builder {
117135
Ok(())
118136
}
119137

138+
pub fn add_ingredient(&self, ingredient_json: &str, format: &str, stream: &dyn Stream) -> Result<()> {
139+
if let Ok(mut builder) = self.builder.try_write() {
140+
let mut stream = StreamAdapter::from(stream);
141+
builder.add_ingredient(ingredient_json, format, &mut stream)?;
142+
} else {
143+
return Err(Error::RwLock);
144+
};
145+
Ok(())
146+
}
147+
120148
/// Sign an asset and write the result to the destination stream
121149
pub fn sign(&self, format: &str, source: &dyn Stream, dest: &dyn Stream, signer: &CallbackSigner) -> Result<Vec<u8>> {
122150
// uniffi doesn't allow mutable parameters, so we we use an adapter

src/signer_info.rs

Lines changed: 0 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,6 @@ use serde::Deserialize;
1616
use crate::{Error, Result};
1717

1818

19-
/// Defines the callback interface for a signer
20-
pub trait SignerCallback: Send + Sync {
21-
/// Read a stream of bytes from the stream
22-
fn sign(&self, bytes: Vec<u8>) -> Result<Vec<u8>>;
23-
}
24-
25-
/// Configuration for a Signer
26-
#[repr(C)]
27-
pub struct SignerConfig {
28-
/// Returns the algorithm of the Signer.
29-
pub alg: c2pa::SigningAlg,
30-
31-
/// Returns the certificates as a Vec containing a Vec of DER bytes for each certificate.
32-
pub certs: Vec<u8>,
33-
34-
/// URL for time authority to time stamp the signature
35-
pub time_authority_url: Option<String>,
36-
37-
/// Try to fetch OCSP response for the signing cert if available
38-
pub use_ocsp: bool,
39-
}
40-
4119
/// SignerInfo provides the information needed to create a signer
4220
/// and sign a manifest.
4321
///
@@ -80,64 +58,5 @@ impl SignerInfo {
8058
.map_err(Error::from_c2pa_error)
8159
}
8260

83-
// /// Create a signer from the SignerInfo
84-
// pub fn callback_signer(&self, callback: Box<SignerCallback>) -> Result<Box<dyn Signer>> {
85-
// let cb_sign = CallbackSigner::new(
86-
// callback,
87-
// self.alg()?,
88-
// self.sign_cert.clone(),
89-
// self.ta_url.clone(),
90-
// );
91-
// Ok(Box::new(cb_sign))
92-
// }
93-
}
94-
95-
pub struct CallbackSigner {
96-
callback: Box<dyn SignerCallback>,
97-
alg: SigningAlg,
98-
sign_certs: Vec<u8>,
99-
ta_url: Option<String>,
100-
}
101-
102-
impl CallbackSigner {
103-
pub fn new(
104-
callback: Box<dyn SignerCallback>,
105-
config: SignerConfig,
106-
// alg: SigningAlg,
107-
// sign_certs: Vec<u8>,
108-
// ta_url: Option<String>,
109-
) -> Self {
110-
Self {
111-
callback,
112-
alg: config.alg,
113-
sign_certs: config.certs,
114-
ta_url: config.time_authority_url,
115-
}
116-
}
11761
}
11862

119-
impl Signer for CallbackSigner {
120-
fn sign(&self, data: &[u8]) -> c2pa::Result<Vec<u8>> {
121-
self.callback
122-
.sign(data.to_vec())
123-
.map_err(|e| c2pa::Error::BadParam(e.to_string()))
124-
}
125-
126-
fn alg(&self) -> SigningAlg {
127-
self.alg
128-
}
129-
130-
fn certs(&self) -> c2pa::Result<Vec<Vec<u8>>> {
131-
let mut pems =
132-
pem::parse_many(&self.sign_certs).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?;
133-
Ok(pems.drain(..).map(|p| p.into_contents()).collect())
134-
}
135-
136-
fn reserve_size(&self) -> usize {
137-
20000
138-
}
139-
140-
fn time_authority_url(&self) -> Option<String> {
141-
self.ta_url.clone()
142-
}
143-
}

tests/c2pa_api.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,23 @@
2727
# ManifestStoreReader = c2pa.ManifestStoreReader
2828
class Reader(c2pa.Reader):
2929
def __init__(self, format, stream):
30-
self.format = format
31-
self.stream = C2paStream(stream)
3230
super().__init__()
31+
self.read(format, C2paStream(stream))
3332

34-
def from_file(path: str, format=None):
33+
@classmethod
34+
def from_file(cls, path: str, format=None):
3535
file = open(path, "rb")
3636
if format is None:
3737
# determine the format from the file extension
3838
format = os.path.splitext(path)[1][1:]
39-
reader = Reader(format, file)
40-
return reader
41-
42-
def read(self):
43-
return super().read(self.format, self.stream)
39+
return cls(format, file)
4440

4541
def resource(self, uri, stream) -> None:
46-
super().resource(uri, C2paStream(stream))
42+
return super().resource(uri, C2paStream(stream))
4743

4844
def resource_file(self, uri, path) -> None:
4945
file = open(path, "wb")
50-
self.resource(uri, file)
46+
return self.resource(uri, file)
5147

5248

5349
class Builder(c2pa.Builder):
@@ -60,24 +56,34 @@ def __init__(self, signer, manifest = None):
6056
def set_manifest(self, manifest):
6157
if not isinstance(manifest, str):
6258
manifest = json.dumps(manifest)
63-
self.with_json(manifest)
59+
super().with_json(manifest)
6460
return self
6561

6662
def add_resource(self, uri, stream):
67-
return super().add_resource(uri, stream)
63+
return super().add_resource(uri, C2paStream(stream))
6864

6965
def add_resource_file(self, uri, path):
70-
stream = C2paStream.open_file(path, "rb")
71-
return self.add_resource(uri, stream)
66+
file = open(path, "rb")
67+
return self.add_resource(uri, file)
68+
69+
def add_ingredient(self, ingredient, format, stream):
70+
if not isinstance(ingredient, str):
71+
ingredient = json.dumps(ingredient)
72+
return super().add_ingredient(ingredient, format, C2paStream(stream))
73+
74+
def add_ingredient_file(self, ingredient, path):
75+
format = os.path.splitext(path)[1][1:]
76+
file = open(path, "rb")
77+
return self.add_ingredient(ingredient, format, file)
7278

73-
def sign_stream(self, format, input, output=None):
74-
return self.sign(format, input, output, self.signer)
79+
def sign(self, format, input, output=None):
80+
return super().sign(format, C2paStream(input), C2paStream(output), self.signer)
7581

7682
def sign_file(self, sourcePath, outputPath):
77-
format = "image/jpeg" #sourcePath.extension[1:]
78-
input = C2paStream.open_file(sourcePath, "rb")
79-
output = C2paStream.open_file(outputPath, "wb")
80-
return self.sign_stream(format, input, output)
83+
format = os.path.splitext(outputPath)[1][1:]
84+
input = open(sourcePath, "rb")
85+
output = open(outputPath, "wb")
86+
return self.sign(format, input, output)
8187

8288

8389
# Implements a C2paStream given a stream handle

tests/fixtures/A_thumbnail.jpg

60.3 KB
Loading

0 commit comments

Comments
 (0)