diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9384e84..0f4d7fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: diff --git a/Cargo.toml b/Cargo.toml index 79704e8..b070d74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/README.md b/README.md index 0620bd4..4dce63e 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/c2pa/__init__.py b/c2pa/__init__.py index b36df1c..dabc384 100644 --- a/c2pa/__init__.py +++ b/c2pa/__init__.py @@ -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'] \ No newline at end of file +__all__ = ['Reader', 'Builder', 'create_signer', 'sign_ps256', 'Error', 'SigningAlg', 'sdk_version', 'version', 'create_remote_signer'] \ No newline at end of file diff --git a/c2pa/c2pa_api/c2pa_api.py b/c2pa/c2pa_api/c2pa_api.py index 3f60404..897b15c 100644 --- a/c2pa/c2pa_api/c2pa_api.py +++ b/c2pa/c2pa_api/c2pa_api.py @@ -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. @@ -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 @@ -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): @@ -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 diff --git a/src/c2pa.udl b/src/c2pa.udl index 671a3ae..37d3acb 100644 --- a/src/c2pa.udl +++ b/src/c2pa.udl @@ -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 { diff --git a/src/callback_signer.rs b/src/callback_signer.rs index 99b7c36..8e63ddd 100644 --- a/src/callback_signer.rs +++ b/src/callback_signer.rs @@ -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; @@ -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, +} + +pub struct RemoteSigner { + signer_callback : Box, + alg: SigningAlg, + reserve_size: u32, +} + +impl c2pa::Signer for RemoteSigner { + fn alg(&self) -> SigningAlg { + self.alg + } + + fn certs(&self) -> c2pa::Result>> { + Ok(Vec::new()) + } + + // signer will return a COSE structure + fn direct_cose_handling(&self) -> bool { + true + } + + fn sign(&self, data: &[u8]) -> c2pa::Result> { + 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 { @@ -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, + 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 { &self.signer } } diff --git a/src/lib.rs b/src/lib.rs index 7e44092..c23ea7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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); @@ -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) } @@ -224,7 +224,7 @@ impl Builder { pub fn sign_file(&self, signer: &CallbackSigner, source: &str, dest: &str) -> Result> { 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) }