Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Run formatter on all the python files #75

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Purpose: Generate the scaffolding for the uniffi library.
fn main() {
uniffi::generate_scaffolding("./src/c2pa.udl").unwrap();
let _result = uniffi::generate_scaffolding("./src/c2pa.udl");
}
35 changes: 31 additions & 4 deletions c2pa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,35 @@
# specific language governing permissions and limitations under
# each license.

from .c2pa_api import Reader, Builder, create_signer, create_remote_signer, sign_ps256
from .c2pa import Error, SigningAlg, CallbackSigner, sdk_version, version
from .c2pa.c2pa import _UniffiConverterTypeSigningAlg, _UniffiConverterTypeReader, _UniffiRustBuffer
from .c2pa_api import (
Reader,
Builder,
create_signer,
create_remote_signer,
sign_ps256,
load_settings_file,
)
from .c2pa import Error, SigningAlg, CallbackSigner, sdk_version, version, load_settings
from .c2pa.c2pa import (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I never saw the syntax with parenthesis before, hope it doesn't have adverse strange side-effects. After researching it, that's allowed only for Python 3.10 and later, so Python-wise should be fine.

_UniffiConverterTypeSigningAlg,
_UniffiConverterTypeReader,
_UniffiRustBuffer,
)

__all__ = ['Reader', 'Builder', 'CallbackSigner', 'create_signer', 'sign_ps256', 'Error', 'SigningAlg', 'sdk_version', 'version', 'create_remote_signer', '_UniffiConverterTypeSigningAlg', '_UniffiRustBuffer', '_UniffiConverterTypeReader']
__all__ = [
"Reader",
"Builder",
"CallbackSigner",
"create_signer",
"sign_ps256",
"Error",
"SigningAlg",
"sdk_version",
"version",
"load_settings",
"load_settings_file",
"create_remote_signer",
"_UniffiConverterTypeSigningAlg",
"_UniffiRustBuffer",
"_UniffiConverterTypeReader",
]
2 changes: 1 addition & 1 deletion c2pa/c2pa_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .c2pa_api import *
from .c2pa_api import *
50 changes: 34 additions & 16 deletions c2pa/c2pa_api/c2pa_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@
import tempfile

PROJECT_PATH = os.getcwd()
SOURCE_PATH = os.path.join(
PROJECT_PATH,"target","python"
)
SOURCE_PATH = os.path.join(PROJECT_PATH, "target", "python")
sys.path.append(SOURCE_PATH)

import c2pa.c2pa as api

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


# Reader is used to read a manifest store from a stream or file.
# It performs full validation on the manifest store.
# It also supports writing resources to a stream or file.
Expand All @@ -37,7 +36,9 @@ class Reader(api.Reader):
def __init__(self, format, stream, manifest_data=None):
super().__init__()
if manifest_data is not None:
self.from_manifest_data_and_stream(manifest_data, format, C2paStream(stream))
self.from_manifest_data_and_stream(
manifest_data, format, C2paStream(stream)
)
else:
self.from_stream(format, C2paStream(stream))

Expand Down Expand Up @@ -67,6 +68,7 @@ def resource_to_file(self, uri, path) -> None:
with open(path, "wb") as file:
return self.resource_to_stream(uri, file)


# The Builder is used to construct a new Manifest and add it to a stream or file.
# The initial manifest is defined by a Manifest Definition dictionary.
# It supports adding resources from a stream or file.
Expand Down Expand Up @@ -130,7 +132,7 @@ def from_archive(cls, stream):
super().from_archive(self, C2paStream(stream))
return self

def sign(self, signer, format, input, output = None):
def sign(self, signer, format, input, output=None):
return super().sign(signer, format, C2paStream(input), C2paStream(output))

def sign_file(self, signer, sourcePath, outputPath):
Expand All @@ -145,7 +147,7 @@ def __init__(self, stream):
self.stream = stream

def read_stream(self, length: int) -> bytes:
#print("Reading " + str(length) + " bytes")
# print("Reading " + str(length) + " bytes")
return self.stream.read(length)

def seek_stream(self, pos: int, mode: api.SeekMode) -> int:
Expand All @@ -154,11 +156,11 @@ def seek_stream(self, pos: int, mode: api.SeekMode) -> int:
whence = 1
elif mode is api.SeekMode.END:
whence = 2
#print("Seeking to " + str(pos) + " with whence " + str(whence))
# print("Seeking to " + str(pos) + " with whence " + str(whence))
return self.stream.seek(pos, whence)

def write_stream(self, data: str) -> int:
#print("Writing " + str(len(data)) + " bytes")
# print("Writing " + str(len(data)) + " bytes")
return self.stream.write(data)

def flush_stream(self) -> None:
Expand All @@ -178,11 +180,12 @@ def __init__(self, callback):


# Convenience class so we can just pass in a callback function
#class CallbackSigner(c2pa.CallbackSigner):
# class CallbackSigner(c2pa.CallbackSigner):
# def __init__(self, callback, alg, certs, timestamp_url=None):
# cb = SignerCallback(callback)
# super().__init__(cb, alg, certs, timestamp_url)


# Creates a Signer given a callback and configuration values
# It is used by the Builder class to sign the asset
#
Expand All @@ -196,6 +199,7 @@ 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
Expand All @@ -218,29 +222,35 @@ def convert_to_alg(alg):
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()
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
def sign_ps256_shell(data: bytes, key_path: str) -> bytes:
with tempfile.NamedTemporaryFile() as bytes:
bytes.write(data)
signature = tempfile.NamedTemporaryFile()
os.system("openssl dgst -sha256 -sign {} -out {} {}".format(key_path, signature.name, bytes.name))
os.system(
"openssl dgst -sha256 -sign {} -out {} {}".format(
key_path, signature.name, bytes.name
)
)
return signature.read()


# Example of using python crypto to sign data using openssl with Ps256
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding


def sign_ps256(data: bytes, key: bytes) -> bytes:
private_key = serialization.load_pem_private_key(
key,
Expand All @@ -249,9 +259,17 @@ def sign_ps256(data: bytes, key: bytes) -> bytes:
signature = private_key.sign(
data,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
hashes.SHA256(),
)
return signature


def load_settings_file(path: str, format=None):
with open(path, "r") as file:
if format is None:
# determine the format from the file extension
format = os.path.splitext(path)[1][1:]
settings = file.read()
api.load_settings(settings, format)
2 changes: 2 additions & 0 deletions src/c2pa.udl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
namespace c2pa {
string version();
string sdk_version();
[Throws=Error]
void load_settings([ByRef] string format, [ByRef] string settings);
};

[Error]
Expand Down
15 changes: 14 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use std::env;
use std::sync::RwLock;

pub use c2pa::{Signer, SigningAlg};
pub use c2pa::{Signer, SigningAlg, settings::load_settings_from_str};

/// these all need to be public so that the uniffi macro can see them
mod error;
Expand Down Expand Up @@ -45,6 +45,11 @@ pub fn sdk_version() -> String {
)
}

pub fn load_settings(settings: &str, format: &str) -> Result<()> {
load_settings_from_str(settings, format)?;
Ok(())
}

pub struct Reader {
reader: RwLock<c2pa::Reader>,
}
Expand Down Expand Up @@ -102,6 +107,14 @@ impl Reader {
}
}

// pub fn validation_state(&self) -> Result<ValidationState> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it debug code?

// if let Ok(st) = self.reader.validation_state() {
// Ok(st.validation_state())
// } else {
// Err(Error::RwLock)
// }
// }

pub fn resource_to_stream(&self, uri: &str, stream: &dyn Stream) -> Result<u64> {
if let Ok(reader) = self.reader.try_read() {
let mut stream = StreamAdapter::from(stream);
Expand Down
71 changes: 49 additions & 22 deletions tests/benchmark.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,65 @@
from c2pa import Builder, Error, Reader, SigningAlg, create_signer, sdk_version, sign_ps256
# Copyright 2024 Adobe. All rights reserved.
# This file is licensed to you under the Apache License,
# Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
# or the MIT license (http://opensource.org/licenses/MIT),
# at your option.
# Unless required by applicable law or agreed to in writing,
# this software is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
# implied. See the LICENSE-MIT and LICENSE-APACHE files for the
# specific language governing permissions and limitations under
# each license.

from c2pa import (
Builder,
Error,
Reader,
SigningAlg,
create_signer,
sdk_version,
sign_ps256,
)
import os
import io

PROJECT_PATH = os.getcwd()

testPath = os.path.join(PROJECT_PATH, "tests", "fixtures", "C.jpg")

manifestDefinition = {
"claim_generator": "python_test",
"claim_generator_info": [{
"name": "python_test",
"version": "0.0.1",
}],
"claim_generator_info": [
{
"name": "python_test",
"version": "0.0.1",
}
],
"format": "image/jpeg",
"title": "Python Test Image",
"ingredients": [],
"assertions": [
{ 'label': 'stds.schema-org.CreativeWork',
'data': {
'@context': 'http://schema.org/',
'@type': 'CreativeWork',
'author': [
{ '@type': 'Person',
'name': 'Gavin Peacock'
}
]
{
"label": "stds.schema-org.CreativeWork",
"data": {
"@context": "http://schema.org/",
"@type": "CreativeWork",
"author": [{"@type": "Person", "name": "Gavin Peacock"}],
},
'kind': 'Json'
"kind": "Json",
}
]
],
}
private_key = open("tests/fixtures/ps256.pem","rb").read()
private_key = open("tests/fixtures/ps256.pem", "rb").read()


# Define a function that signs data with PS256 using a private key
def sign(data: bytes) -> bytes:
print("date len = ", len(data))
return sign_ps256(data, private_key)


# load the public keys from a pem file
certs = open("tests/fixtures/ps256.pub","rb").read()
certs = open("tests/fixtures/ps256.pub", "rb").read()

# Create a local Ps256 signer with certs and a timestamp server
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
Expand All @@ -50,22 +72,27 @@ def sign(data: bytes) -> bytes:
testPath = "tests/fixtures/c.jpg"
outputPath = "target/python_out.jpg"


def test_files_build():
# Delete the output file if it exists
# Delete the output file if it exists
if os.path.exists(outputPath):
os.remove(outputPath)
builder.sign_file(signer, testPath, outputPath)


def test_streams_build():
#with open(testPath, "rb") as file:
# with open(testPath, "rb") as file:
output = io.BytesIO(bytearray())
builder.sign(signer, "image/jpeg", io.BytesIO(source), output)


def test_func(benchmark):
benchmark(test_files_build)


def test_streams(benchmark):
benchmark(test_streams_build)

#def test_signer(benchmark):
# benchmark(sign_ps256, data, private_key)

# def test_signer(benchmark):
# benchmark(sign_ps256, data, private_key)
Loading
Loading