diff --git a/Makefile b/Makefile index 8435e30f..9129b17b 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ build-python: # Performs a complete rebuild of the development environment rebuild: clean-c2pa-env install-deps download-native-artifacts build-python - @echo "Development rebuild done!" + @echo "Development rebuild done" run-examples: python3 ./examples/sign.py diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index 7bb25a03..5795f6be 100644 --- a/src/c2pa/c2pa.py +++ b/src/c2pa/c2pa.py @@ -123,6 +123,19 @@ class C2paSigningAlg(enum.IntEnum): ED25519 = 6 +# Mapping from C2paSigningAlg enum to string representation, +# as the enum value currently maps by default to an integer value. +_ALG_TO_STRING_BYTES_MAPPING = { + C2paSigningAlg.ES256: b"es256", + C2paSigningAlg.ES384: b"es384", + C2paSigningAlg.ES512: b"es512", + C2paSigningAlg.PS256: b"ps256", + C2paSigningAlg.PS384: b"ps384", + C2paSigningAlg.PS512: b"ps512", + C2paSigningAlg.ED25519: b"ed25519", +} + + # Define callback types ReadCallback = ctypes.CFUNCTYPE( ctypes.c_ssize_t, @@ -205,6 +218,45 @@ class C2paSignerInfo(ctypes.Structure): ("ta_url", ctypes.c_char_p), ] + def __init__(self, alg, sign_cert, private_key, ta_url): + """Initialize C2paSignerInfo with optional parameters. + + Args: + alg: The signing algorithm, either as a C2paSigningAlg enum or string or bytes + (will be converted accordingly to bytes for native library use) + sign_cert: The signing certificate as a string + private_key: The private key as a string + ta_url: The timestamp authority URL as bytes + """ + # Handle alg parameter: can be C2paSigningAlg enum or string (or bytes), convert as needed + if isinstance(alg, C2paSigningAlg): + # Convert enum to string representation + alg_str = _ALG_TO_STRING_BYTES_MAPPING.get(alg) + if alg_str is None: + raise ValueError(f"Unsupported signing algorithm: {alg}") + alg = alg_str + elif isinstance(alg, str): + # String to bytes, as requested by native lib + alg = alg.encode('utf-8') + elif isinstance(alg, bytes): + # In bytes already + pass + else: + raise TypeError(f"alg must be C2paSigningAlg enum, string, or bytes, got {type(alg)}") + + # Handle ta_url parameter: allow string or bytes, convert string to bytes as needed + if isinstance(ta_url, str): + # String to bytes, as requested by native lib + ta_url = ta_url.encode('utf-8') + elif isinstance(ta_url, bytes): + # In bytes already + pass + else: + raise TypeError(f"ta_url must be string or bytes, got {type(ta_url)}") + + # Call parent constructor with processed values + super().__init__(alg, sign_cert, private_key, ta_url) + class C2paReader(ctypes.Structure): """Opaque structure for reader context.""" diff --git a/tests/fixtures/files-for-reading-tests/C_with_CAWG_data.jpg b/tests/fixtures/files-for-reading-tests/C_with_CAWG_data.jpg index a397bc9a..dd93c44d 100644 Binary files a/tests/fixtures/files-for-reading-tests/C_with_CAWG_data.jpg and b/tests/fixtures/files-for-reading-tests/C_with_CAWG_data.jpg differ diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index e40c27c0..ed6d5c4a 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -1493,7 +1493,69 @@ def test_read_file(self): self.assertIn("manifests", file_data) self.assertIn(expected_manifest_id, file_data["manifests"]) - def test_sign_file(self): + def test_sign_file_alg_as_enum(self): + """Test signing a file with C2PA manifest.""" + # Set up test paths + temp_data_dir = os.path.join(self.data_dir, "temp_data") + os.makedirs(temp_data_dir, exist_ok=True) + output_path = os.path.join(temp_data_dir, "signed_output.jpg") + + # Load test certificates and key + with open(os.path.join(self.data_dir, "es256_certs.pem"), "rb") as cert_file: + certs = cert_file.read() + with open(os.path.join(self.data_dir, "es256_private.key"), "rb") as key_file: + key = key_file.read() + + # Create signer info + signer_info = C2paSignerInfo( + alg=SigningAlg.ES256, + sign_cert=certs, + private_key=key, + ta_url=b"http://timestamp.digicert.com" + ) + + # Create a simple manifest + manifest = { + "claim_generator": "python_internals_test", + "claim_generator_info": [{ + "name": "python_internals_test", + "version": "0.0.1", + }], + "format": "image/jpeg", + "title": "Python Test Signed Image", + "ingredients": [], + "assertions": [ + { + "label": "c2pa.actions", + "data": { + "actions": [ + { + "action": "c2pa.opened" + } + ] + } + } + ] + } + + # Convert manifest to JSON string + manifest_json = json.dumps(manifest) + + try: + # Sign the file + result_json = sign_file( + self.testPath, + output_path, + manifest_json, + signer_info + ) + + finally: + # Clean up + if os.path.exists(output_path): + os.remove(output_path) + + def test_sign_file_alg_as_bytes(self): """Test signing a file with C2PA manifest.""" # Set up test paths temp_data_dir = os.path.join(self.data_dir, "temp_data")