Skip to content

Commit

Permalink
Make underlying plumbing more generic (#54)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
tmathern authored Oct 24, 2024
1 parent ed7f839 commit a533220
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 13 deletions.
1 change: 0 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ jobs:
# If we're running on debian-based system.
apt update -y && apt-get install -y libssl-dev openssl pkg-config
fi
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ serde_json = "1.0"
thiserror = "1.0.49"
uniffi = "0.24.1"
openssl-src = "=300.3.1" # Required for openssl-sys
log = "0.4.21"
env_logger = "0.11.3"

[build-dependencies]
uniffi = { version = "0.24.1", features = ["build"] }
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ pip install -U pytest
python3 -m build --wheel
```

Note: To peek at the Python code (uniffi generated and non-generated), run `maturin develop` and look in the c2pa folder.

### ManyLinux build

Build using [manylinux](https://github.com/pypa/manylinux) by using a Docker image as follows:
Expand Down
4 changes: 2 additions & 2 deletions c2pa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# specific language governing permissions and limitations under
# each license.

from .c2pa_api import Reader, Builder, create_signer, sign_ps256
from .c2pa_api import Reader, Builder, create_signer, create_remote_signer, sign_ps256
from .c2pa import Error, SigningAlg, sdk_version, version

__all__ = ['Reader', 'Builder', 'create_signer', 'sign_ps256', 'Error', 'SigningAlg', 'sdk_version', 'version']
__all__ = ['Reader', 'Builder', 'create_signer', 'sign_ps256', 'Error', 'SigningAlg', 'sdk_version', 'version', 'create_remote_signer']
35 changes: 32 additions & 3 deletions c2pa/c2pa_api/c2pa_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
sys.path.append(SOURCE_PATH)

import c2pa.c2pa as api
#from c2pa import Error, SigningAlg, version, sdk_version

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

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



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


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


# Because we "share" SigningAlg enum in-between bindings,
# seems we need to manually coerce the enum types,
# like unffi itself does too
def convert_to_alg(alg):
match str(alg):
case "SigningAlg.ES256":
return api.SigningAlg.ES256
case "SigningAlg.ES384":
return api.SigningAlg.ES384
case "SigningAlg.ES512":
return api.SigningAlg.ES512
case "SigningAlg.PS256":
return api.SigningAlg.PS256
case "SigningAlg.PS384":
return api.SigningAlg.PS384
case "SigningAlg.PS512":
return api.SigningAlg.PS512
case "SigningAlg.ED25519":
return api.SigningAlg.ED25519
case _:
raise ValueError("Unsupported signing algorithm: " + str(alg))

# Creates a special case signer that uses direct COSE handling
# The callback signer should also define the signing algorithm to use
# And a way to find out the needed reserve size
def create_remote_signer(callback):
return api.CallbackSigner.new_from_signer(
callback,
convert_to_alg(callback.alg()),
callback.reserve_size()
)

# Example of using openssl in an os shell to sign data using Ps256
# Note: the openssl command line tool must be installed for this to work
Expand Down
3 changes: 3 additions & 0 deletions src/c2pa.udl
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ callback interface SignerCallback {

interface CallbackSigner {
constructor(SignerCallback callback, SigningAlg alg, bytes certs, string? ta_url);

[Name=new_from_signer]
constructor(SignerCallback callback, SigningAlg alg, u32 reserve_size);
};

interface Builder {
Expand Down
54 changes: 50 additions & 4 deletions src/callback_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
// specific language governing permissions and limitations under
// each license.

use c2pa::SigningAlg;
use c2pa::{SigningAlg, Signer};
use log::debug;

use crate::Result;

Expand All @@ -24,7 +25,36 @@ pub trait SignerCallback: Send + Sync {
///
/// Uniffi callbacks are only supported as a method in a structure, so this is a workaround
pub struct CallbackSigner {
signer: c2pa::CallbackSigner,
signer: Box<dyn Signer + Sync + Send>,
}

pub struct RemoteSigner {
signer_callback : Box<dyn SignerCallback>,
alg: SigningAlg,
reserve_size: u32,
}

impl c2pa::Signer for RemoteSigner {
fn alg(&self) -> SigningAlg {
self.alg
}

fn certs(&self) -> c2pa::Result<Vec<Vec<u8>>> {
Ok(Vec::new())
}

// signer will return a COSE structure
fn direct_cose_handling(&self) -> bool {
true
}

fn sign(&self, data: &[u8]) -> c2pa::Result<Vec<u8>> {
self.signer_callback.sign(data.to_vec()).map_err(|e| c2pa::Error::BadParam(e.to_string()))
}

fn reserve_size(&self) -> usize {
self.reserve_size as usize // TODO: Find better conversion for usize
}
}

impl CallbackSigner {
Expand All @@ -45,11 +75,27 @@ impl CallbackSigner {
if let Some(url) = ta_url {
signer = signer.set_tsa_url(url);
}
Self { signer }

Self { signer: Box::new(signer) }
}

pub fn new_from_signer(
callback: Box<dyn SignerCallback>,
alg: SigningAlg,
reserve_size: u32,
) -> Self {
debug!("c2pa-python: CallbackSigner -> new_from_signer");
let signer = RemoteSigner {
signer_callback: callback,
alg,
reserve_size
};

Self { signer: Box::new(signer) }
}

/// The python Builder wrapper sign function calls this
pub fn signer(&self) -> &c2pa::CallbackSigner {
pub fn signer(&self) -> &Box<dyn c2pa::Signer + Sync + Send> {
&self.signer
}
}
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ impl Builder {
Ok(())
}

/// Set to true to disable embedding a manifest
/// Set to true to disable embedding a manifest
pub fn set_no_embed(&self) -> Result<()> {
if let Ok(mut builder) = self.builder.try_write() {
builder.set_no_embed(true);
Expand Down Expand Up @@ -214,7 +214,7 @@ impl Builder {
let mut dest = StreamAdapter::from(dest);
if let Ok(mut builder) = self.builder.try_write() {
let signer = (*signer).signer();
Ok(builder.sign(signer, format, &mut source, &mut dest)?)
Ok(builder.sign(signer.as_ref(), format, &mut source, &mut dest)?)
} else {
Err(Error::RwLock)
}
Expand All @@ -224,7 +224,7 @@ impl Builder {
pub fn sign_file(&self, signer: &CallbackSigner, source: &str, dest: &str) -> Result<Vec<u8>> {
if let Ok(mut builder) = self.builder.try_write() {
let signer = (*signer).signer();
Ok(builder.sign_file(signer, source, dest)?)
Ok(builder.sign_file(signer.as_ref(), source, dest)?)
} else {
Err(Error::RwLock)
}
Expand Down

0 comments on commit a533220

Please sign in to comment.