Skip to content

Commit 4e81626

Browse files
committed
fix: Clean up
1 parent 9b41397 commit 4e81626

File tree

3 files changed

+90
-13
lines changed

3 files changed

+90
-13
lines changed

requirements-dev.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ pytest-benchmark>=5.1.0
1212
requests>=2.0.0
1313

1414
# Code formatting
15-
autopep8==2.0.4 # For automatic code formatting
15+
autopep8==2.0.4 # For automatic code formatting
16+
17+
# Test dependencies (for callback signers)
18+
cryptography==45.0.4

src/c2pa/c2pa.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,14 +1308,28 @@ def from_callback(
13081308
C2paError: If there was an error creating the signer
13091309
C2paError.Encoding: If the certificate data or TSA URL contains invalid UTF-8 characters
13101310
"""
1311+
# Define error messages locally since they're instance attributes
1312+
error_messages = {
1313+
'closed_error': "Signer is closed",
1314+
'cleanup_error': "Error during cleanup: {}",
1315+
'signer_cleanup': "Error cleaning up signer: {}",
1316+
'size_error': "Error getting reserve size: {}",
1317+
'callback_error': "Error in signer callback: {}",
1318+
'info_error': "Error creating signer from info: {}",
1319+
'invalid_data': "Invalid data for signing: {}",
1320+
'invalid_certs': "Invalid certificate data: {}",
1321+
'invalid_tsa': "Invalid TSA URL: {}",
1322+
'encoding_error': "Invalid UTF-8 characters in input: {}"
1323+
}
1324+
13111325
# Validate inputs before creating
13121326
if not certs:
13131327
raise C2paError(
1314-
cls._error_messages['invalid_certs'].format("Missing certificate data"))
1328+
error_messages['invalid_certs'].format("Missing certificate data"))
13151329

13161330
if tsa_url and not tsa_url.startswith(('http://', 'https://')):
13171331
raise C2paError(
1318-
cls._error_messages['invalid_tsa'].format("Invalid TSA URL format"))
1332+
error_messages['invalid_tsa'].format("Invalid TSA URL format"))
13191333

13201334
# Create a wrapper callback that handles errors and memory management
13211335
def wrapped_callback(context, data_ptr, data_len, signed_bytes_ptr, signed_len):
@@ -1349,7 +1363,7 @@ def wrapped_callback(context, data_ptr, data_len, signed_bytes_ptr, signed_len):
13491363
return actual_len
13501364
except Exception as e:
13511365
print(
1352-
cls._error_messages['callback_error'].format(
1366+
error_messages['callback_error'].format(
13531367
str(e)), file=sys.stderr)
13541368
return 0
13551369

@@ -1359,7 +1373,7 @@ def wrapped_callback(context, data_ptr, data_len, signed_bytes_ptr, signed_len):
13591373
tsa_url_bytes = tsa_url.encode('utf-8') if tsa_url else None
13601374
except UnicodeError as e:
13611375
raise C2paError.Encoding(
1362-
cls._error_messages['encoding_error'].format(
1376+
error_messages['encoding_error'].format(
13631377
str(e)))
13641378

13651379
# Create the signer with the wrapped callback
@@ -1385,7 +1399,7 @@ def wrapped_callback(context, data_ptr, data_len, signed_bytes_ptr, signed_len):
13851399
# Initialize the signer instance
13861400
signer_instance._signer = signer_ptr
13871401
signer_instance._closed = False
1388-
signer_instance._error_messages = cls._error_messages
1402+
signer_instance._error_messages = error_messages
13891403

13901404
return signer_instance
13911405

@@ -1973,7 +1987,7 @@ def wrapped_callback(context, data_ptr, data_len, signed_bytes_ptr, signed_len):
19731987
create_signer._callbacks.append(c_callback) # Keep it alive
19741988

19751989
signer_ptr = _lib.c2pa_signer_create(
1976-
None, # context
1990+
None,
19771991
c_callback, # Use the stored callback
19781992
alg,
19791993
certs_bytes,

tests/test_unit_tests.py

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from cryptography.hazmat.primitives import hashes, serialization
2222
from cryptography.hazmat.primitives.asymmetric import padding
2323
from cryptography.hazmat.backends import default_backend
24+
import tempfile
25+
import shutil
2426

2527
from c2pa import Builder, C2paError as Error, Reader, C2paSigningAlg as SigningAlg, C2paSignerInfo, Signer, sdk_version
2628
from c2pa.c2pa import Stream, read_ingredient_file, read_file, sign_file, load_settings, create_signer
@@ -720,9 +722,6 @@ def test_builder_set_remote_url_no_embed(self):
720722

721723
def test_sign_file(self):
722724
"""Test signing a file using the sign_file method."""
723-
import tempfile
724-
import shutil
725-
726725
# Create a temporary directory for the test
727726
temp_dir = tempfile.mkdtemp()
728727
try:
@@ -753,9 +752,6 @@ def test_sign_file(self):
753752

754753
def test_sign_file_callback_signer(self):
755754
"""Test signing a file using the sign_file method."""
756-
import tempfile
757-
import shutil
758-
759755
# Create a temporary directory for the test
760756
temp_dir = tempfile.mkdtemp()
761757
try:
@@ -819,6 +815,70 @@ def sign_callback(data: bytes) -> bytes:
819815
# Clean up the temporary directory
820816
shutil.rmtree(temp_dir)
821817

818+
def test_sign_file_callback_signer_from_callback(self):
819+
"""Test signing a file using the sign_file method with Signer.from_callback."""
820+
# Create a temporary directory for the test
821+
temp_dir = tempfile.mkdtemp()
822+
try:
823+
# Create a temporary output file path
824+
output_path = os.path.join(temp_dir, "signed_output_from_callback.jpg")
825+
826+
# Use the sign_file method
827+
builder = Builder(self.manifestDefinition)
828+
829+
# Create a real ES256 signing callback
830+
def sign_callback(data: bytes) -> bytes:
831+
"""Real ES256 signing callback that creates actual signatures."""
832+
# Load the private key from the test fixtures
833+
with open(os.path.join(self.data_dir, "es256_private.key"), "rb") as key_file:
834+
private_key_data = key_file.read()
835+
836+
# Load the private key using cryptography
837+
private_key = serialization.load_pem_private_key(
838+
private_key_data,
839+
password=None,
840+
backend=default_backend()
841+
)
842+
843+
# Create the signature using ES256 (ECDSA with SHA-256)
844+
# For ECDSA, we use the signature_algorithm_constructor
845+
from cryptography.hazmat.primitives import hashes
846+
from cryptography.hazmat.primitives.asymmetric import ec
847+
848+
signature = private_key.sign(
849+
data,
850+
ec.ECDSA(hashes.SHA256())
851+
)
852+
853+
return signature
854+
855+
# Create signer with callback using Signer.from_callback
856+
signer = Signer.from_callback(
857+
callback=sign_callback,
858+
alg=SigningAlg.ES256,
859+
certs=self.certs.decode('utf-8'),
860+
tsa_url="http://timestamp.digicert.com"
861+
)
862+
863+
result = builder.sign_file(
864+
source_path=self.testPath,
865+
dest_path=output_path,
866+
signer=signer
867+
)
868+
869+
# Verify the output file was created
870+
self.assertTrue(os.path.exists(output_path))
871+
872+
# Read the signed file and verify the manifest
873+
with open(output_path, "rb") as file:
874+
reader = Reader("image/jpeg", file)
875+
json_data = reader.json()
876+
self.assertIn("Python Test", json_data)
877+
self.assertNotIn("validation_status", json_data)
878+
879+
finally:
880+
# Clean up the temporary directory
881+
shutil.rmtree(temp_dir)
822882

823883
class TestStream(unittest.TestCase):
824884
def setUp(self):

0 commit comments

Comments
 (0)