Skip to content

Commit fa5aa60

Browse files
authored
fix: Support using Alg enum in SignerInfo (#136)
* fix: Extend SignerInfo to support getting in the alg enum * fix: One more check * fix: Docs * fix: Add a clarifying comment * fix: Move mapping closer to enum * fix: Renaming * fix: Fix weird test file * fix: Make API more strict for C2paSignerInfo
1 parent 4e1fb99 commit fa5aa60

File tree

3 files changed

+116
-2
lines changed

3 files changed

+116
-2
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ build-python:
2828

2929
# Performs a complete rebuild of the development environment
3030
rebuild: clean-c2pa-env install-deps download-native-artifacts build-python
31-
@echo "Development rebuild done!"
31+
@echo "Development rebuild done"
3232

3333
run-examples:
3434
python3 ./examples/sign.py

src/c2pa/c2pa.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ class C2paSigningAlg(enum.IntEnum):
123123
ED25519 = 6
124124

125125

126+
# Mapping from C2paSigningAlg enum to string representation,
127+
# as the enum value currently maps by default to an integer value.
128+
_ALG_TO_STRING_BYTES_MAPPING = {
129+
C2paSigningAlg.ES256: b"es256",
130+
C2paSigningAlg.ES384: b"es384",
131+
C2paSigningAlg.ES512: b"es512",
132+
C2paSigningAlg.PS256: b"ps256",
133+
C2paSigningAlg.PS384: b"ps384",
134+
C2paSigningAlg.PS512: b"ps512",
135+
C2paSigningAlg.ED25519: b"ed25519",
136+
}
137+
138+
126139
# Define callback types
127140
ReadCallback = ctypes.CFUNCTYPE(
128141
ctypes.c_ssize_t,
@@ -205,6 +218,45 @@ class C2paSignerInfo(ctypes.Structure):
205218
("ta_url", ctypes.c_char_p),
206219
]
207220

221+
def __init__(self, alg, sign_cert, private_key, ta_url):
222+
"""Initialize C2paSignerInfo with optional parameters.
223+
224+
Args:
225+
alg: The signing algorithm, either as a C2paSigningAlg enum or string or bytes
226+
(will be converted accordingly to bytes for native library use)
227+
sign_cert: The signing certificate as a string
228+
private_key: The private key as a string
229+
ta_url: The timestamp authority URL as bytes
230+
"""
231+
# Handle alg parameter: can be C2paSigningAlg enum or string (or bytes), convert as needed
232+
if isinstance(alg, C2paSigningAlg):
233+
# Convert enum to string representation
234+
alg_str = _ALG_TO_STRING_BYTES_MAPPING.get(alg)
235+
if alg_str is None:
236+
raise ValueError(f"Unsupported signing algorithm: {alg}")
237+
alg = alg_str
238+
elif isinstance(alg, str):
239+
# String to bytes, as requested by native lib
240+
alg = alg.encode('utf-8')
241+
elif isinstance(alg, bytes):
242+
# In bytes already
243+
pass
244+
else:
245+
raise TypeError(f"alg must be C2paSigningAlg enum, string, or bytes, got {type(alg)}")
246+
247+
# Handle ta_url parameter: allow string or bytes, convert string to bytes as needed
248+
if isinstance(ta_url, str):
249+
# String to bytes, as requested by native lib
250+
ta_url = ta_url.encode('utf-8')
251+
elif isinstance(ta_url, bytes):
252+
# In bytes already
253+
pass
254+
else:
255+
raise TypeError(f"ta_url must be string or bytes, got {type(ta_url)}")
256+
257+
# Call parent constructor with processed values
258+
super().__init__(alg, sign_cert, private_key, ta_url)
259+
208260

209261
class C2paReader(ctypes.Structure):
210262
"""Opaque structure for reader context."""

tests/test_unit_tests.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1493,7 +1493,69 @@ def test_read_file(self):
14931493
self.assertIn("manifests", file_data)
14941494
self.assertIn(expected_manifest_id, file_data["manifests"])
14951495

1496-
def test_sign_file(self):
1496+
def test_sign_file_alg_as_enum(self):
1497+
"""Test signing a file with C2PA manifest."""
1498+
# Set up test paths
1499+
temp_data_dir = os.path.join(self.data_dir, "temp_data")
1500+
os.makedirs(temp_data_dir, exist_ok=True)
1501+
output_path = os.path.join(temp_data_dir, "signed_output.jpg")
1502+
1503+
# Load test certificates and key
1504+
with open(os.path.join(self.data_dir, "es256_certs.pem"), "rb") as cert_file:
1505+
certs = cert_file.read()
1506+
with open(os.path.join(self.data_dir, "es256_private.key"), "rb") as key_file:
1507+
key = key_file.read()
1508+
1509+
# Create signer info
1510+
signer_info = C2paSignerInfo(
1511+
alg=SigningAlg.ES256,
1512+
sign_cert=certs,
1513+
private_key=key,
1514+
ta_url=b"http://timestamp.digicert.com"
1515+
)
1516+
1517+
# Create a simple manifest
1518+
manifest = {
1519+
"claim_generator": "python_internals_test",
1520+
"claim_generator_info": [{
1521+
"name": "python_internals_test",
1522+
"version": "0.0.1",
1523+
}],
1524+
"format": "image/jpeg",
1525+
"title": "Python Test Signed Image",
1526+
"ingredients": [],
1527+
"assertions": [
1528+
{
1529+
"label": "c2pa.actions",
1530+
"data": {
1531+
"actions": [
1532+
{
1533+
"action": "c2pa.opened"
1534+
}
1535+
]
1536+
}
1537+
}
1538+
]
1539+
}
1540+
1541+
# Convert manifest to JSON string
1542+
manifest_json = json.dumps(manifest)
1543+
1544+
try:
1545+
# Sign the file
1546+
result_json = sign_file(
1547+
self.testPath,
1548+
output_path,
1549+
manifest_json,
1550+
signer_info
1551+
)
1552+
1553+
finally:
1554+
# Clean up
1555+
if os.path.exists(output_path):
1556+
os.remove(output_path)
1557+
1558+
def test_sign_file_alg_as_bytes(self):
14971559
"""Test signing a file with C2PA manifest."""
14981560
# Set up test paths
14991561
temp_data_dir = os.path.join(self.data_dir, "temp_data")

0 commit comments

Comments
 (0)