Skip to content

Commit a533220

Browse files
authored
Make underlying plumbing more generic (#54)
* Turn into box * Use Trait instead of type * WIP * Change import * Add logs * Add logging deps * Logger * Temp COSE change * COSE handling * WIP * Clean up * Python UDL mapping * Add export * Add a fucntion * Missing export * One more api change * Revert wrapping * Restore mistakenly removed file * Undo wrapping * Add more logs * Add alg support * Make alg param optional * API changes to simplify * Fix typings in UDL * Update bindings * Remove env_logger init * Clean up logs * Add pytest to Linux action for testing * Pytest attempt 2 * Remove maturin from workflow * Update build.yml * Clean up API * Add code doc * Fix doc
1 parent ed7f839 commit a533220

File tree

8 files changed

+94
-13
lines changed

8 files changed

+94
-13
lines changed

.github/workflows/build.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ jobs:
5555
# If we're running on debian-based system.
5656
apt update -y && apt-get install -y libssl-dev openssl pkg-config
5757
fi
58-
5958
- name: Upload wheels
6059
uses: actions/upload-artifact@v3
6160
with:

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ serde_json = "1.0"
1919
thiserror = "1.0.49"
2020
uniffi = "0.24.1"
2121
openssl-src = "=300.3.1" # Required for openssl-sys
22+
log = "0.4.21"
23+
env_logger = "0.11.3"
2224

2325
[build-dependencies]
2426
uniffi = { version = "0.24.1", features = ["build"] }

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ pip install -U pytest
333333
python3 -m build --wheel
334334
```
335335

336+
Note: To peek at the Python code (uniffi generated and non-generated), run `maturin develop` and look in the c2pa folder.
337+
336338
### ManyLinux build
337339

338340
Build using [manylinux](https://github.com/pypa/manylinux) by using a Docker image as follows:

c2pa/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# specific language governing permissions and limitations under
1212
# each license.
1313

14-
from .c2pa_api import Reader, Builder, create_signer, sign_ps256
14+
from .c2pa_api import Reader, Builder, create_signer, create_remote_signer, sign_ps256
1515
from .c2pa import Error, SigningAlg, sdk_version, version
1616

17-
__all__ = ['Reader', 'Builder', 'create_signer', 'sign_ps256', 'Error', 'SigningAlg', 'sdk_version', 'version']
17+
__all__ = ['Reader', 'Builder', 'create_signer', 'sign_ps256', 'Error', 'SigningAlg', 'sdk_version', 'version', 'create_remote_signer']

c2pa/c2pa_api/c2pa_api.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
sys.path.append(SOURCE_PATH)
2424

2525
import c2pa.c2pa as api
26-
#from c2pa import Error, SigningAlg, version, sdk_version
2726

2827
# This module provides a simple Python API for the C2PA library.
2928

@@ -134,7 +133,6 @@ def sign_file(self, signer, sourcePath, outputPath):
134133
return super().sign_file(signer, sourcePath, outputPath)
135134

136135

137-
138136
# Implements a C2paStream given a stream handle
139137
# This is used to pass a file handle to the c2pa library
140138
# It is used by the Reader and Builder classes internally
@@ -166,6 +164,7 @@ def flush_stream(self) -> None:
166164
def open_file(path: str, mode: str) -> api.Stream:
167165
return C2paStream(open(path, mode))
168166

167+
169168
# Internal class to implement signer callbacks
170169
# We need this because the callback expects a class with a sign method
171170
class SignerCallback(api.SignerCallback):
@@ -193,7 +192,37 @@ def __init__(self, callback):
193192
def create_signer(callback, alg, certs, timestamp_url=None):
194193
return api.CallbackSigner(SignerCallback(callback), alg, certs, timestamp_url)
195194

196-
195+
# Because we "share" SigningAlg enum in-between bindings,
196+
# seems we need to manually coerce the enum types,
197+
# like unffi itself does too
198+
def convert_to_alg(alg):
199+
match str(alg):
200+
case "SigningAlg.ES256":
201+
return api.SigningAlg.ES256
202+
case "SigningAlg.ES384":
203+
return api.SigningAlg.ES384
204+
case "SigningAlg.ES512":
205+
return api.SigningAlg.ES512
206+
case "SigningAlg.PS256":
207+
return api.SigningAlg.PS256
208+
case "SigningAlg.PS384":
209+
return api.SigningAlg.PS384
210+
case "SigningAlg.PS512":
211+
return api.SigningAlg.PS512
212+
case "SigningAlg.ED25519":
213+
return api.SigningAlg.ED25519
214+
case _:
215+
raise ValueError("Unsupported signing algorithm: " + str(alg))
216+
217+
# Creates a special case signer that uses direct COSE handling
218+
# The callback signer should also define the signing algorithm to use
219+
# And a way to find out the needed reserve size
220+
def create_remote_signer(callback):
221+
return api.CallbackSigner.new_from_signer(
222+
callback,
223+
convert_to_alg(callback.alg()),
224+
callback.reserve_size()
225+
)
197226

198227
# Example of using openssl in an os shell to sign data using Ps256
199228
# Note: the openssl command line tool must be installed for this to work

src/c2pa.udl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ callback interface SignerCallback {
7474

7575
interface CallbackSigner {
7676
constructor(SignerCallback callback, SigningAlg alg, bytes certs, string? ta_url);
77+
78+
[Name=new_from_signer]
79+
constructor(SignerCallback callback, SigningAlg alg, u32 reserve_size);
7780
};
7881

7982
interface Builder {

src/callback_signer.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
// specific language governing permissions and limitations under
1111
// each license.
1212

13-
use c2pa::SigningAlg;
13+
use c2pa::{SigningAlg, Signer};
14+
use log::debug;
1415

1516
use crate::Result;
1617

@@ -24,7 +25,36 @@ pub trait SignerCallback: Send + Sync {
2425
///
2526
/// Uniffi callbacks are only supported as a method in a structure, so this is a workaround
2627
pub struct CallbackSigner {
27-
signer: c2pa::CallbackSigner,
28+
signer: Box<dyn Signer + Sync + Send>,
29+
}
30+
31+
pub struct RemoteSigner {
32+
signer_callback : Box<dyn SignerCallback>,
33+
alg: SigningAlg,
34+
reserve_size: u32,
35+
}
36+
37+
impl c2pa::Signer for RemoteSigner {
38+
fn alg(&self) -> SigningAlg {
39+
self.alg
40+
}
41+
42+
fn certs(&self) -> c2pa::Result<Vec<Vec<u8>>> {
43+
Ok(Vec::new())
44+
}
45+
46+
// signer will return a COSE structure
47+
fn direct_cose_handling(&self) -> bool {
48+
true
49+
}
50+
51+
fn sign(&self, data: &[u8]) -> c2pa::Result<Vec<u8>> {
52+
self.signer_callback.sign(data.to_vec()).map_err(|e| c2pa::Error::BadParam(e.to_string()))
53+
}
54+
55+
fn reserve_size(&self) -> usize {
56+
self.reserve_size as usize // TODO: Find better conversion for usize
57+
}
2858
}
2959

3060
impl CallbackSigner {
@@ -45,11 +75,27 @@ impl CallbackSigner {
4575
if let Some(url) = ta_url {
4676
signer = signer.set_tsa_url(url);
4777
}
48-
Self { signer }
78+
79+
Self { signer: Box::new(signer) }
80+
}
81+
82+
pub fn new_from_signer(
83+
callback: Box<dyn SignerCallback>,
84+
alg: SigningAlg,
85+
reserve_size: u32,
86+
) -> Self {
87+
debug!("c2pa-python: CallbackSigner -> new_from_signer");
88+
let signer = RemoteSigner {
89+
signer_callback: callback,
90+
alg,
91+
reserve_size
92+
};
93+
94+
Self { signer: Box::new(signer) }
4995
}
5096

5197
/// The python Builder wrapper sign function calls this
52-
pub fn signer(&self) -> &c2pa::CallbackSigner {
98+
pub fn signer(&self) -> &Box<dyn c2pa::Signer + Sync + Send> {
5399
&self.signer
54100
}
55101
}

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ impl Builder {
134134
Ok(())
135135
}
136136

137-
/// Set to true to disable embedding a manifest
137+
/// Set to true to disable embedding a manifest
138138
pub fn set_no_embed(&self) -> Result<()> {
139139
if let Ok(mut builder) = self.builder.try_write() {
140140
builder.set_no_embed(true);
@@ -214,7 +214,7 @@ impl Builder {
214214
let mut dest = StreamAdapter::from(dest);
215215
if let Ok(mut builder) = self.builder.try_write() {
216216
let signer = (*signer).signer();
217-
Ok(builder.sign(signer, format, &mut source, &mut dest)?)
217+
Ok(builder.sign(signer.as_ref(), format, &mut source, &mut dest)?)
218218
} else {
219219
Err(Error::RwLock)
220220
}
@@ -224,7 +224,7 @@ impl Builder {
224224
pub fn sign_file(&self, signer: &CallbackSigner, source: &str, dest: &str) -> Result<Vec<u8>> {
225225
if let Ok(mut builder) = self.builder.try_write() {
226226
let signer = (*signer).signer();
227-
Ok(builder.sign_file(signer, source, dest)?)
227+
Ok(builder.sign_file(signer.as_ref(), source, dest)?)
228228
} else {
229229
Err(Error::RwLock)
230230
}

0 commit comments

Comments
 (0)