Skip to content

Commit 4dc3831

Browse files
authored
fix: Examples update (#129)
* fix: Update examples * fix: Add a v2 test * fix: Add v2 callback signer test * fix: One more example * fix: More examples * fix: Remove debug log * Update README.md * fix: Docs review comments
1 parent 423e82b commit 4dc3831

File tree

5 files changed

+303
-63
lines changed

5 files changed

+303
-63
lines changed

examples/README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ The examples use asset files from the `tests/fixtures` directory, save the resul
88
The [`examples/sign.py`](https://github.com/contentauth/c2pa-python/blob/main/examples/sign.py) script shows how to sign an asset with a C2PA manifest and verify the asset.
99

1010

11+
The `examples/sign.py` script shows how to sign an asset with a C2PA manifest and verify it using a callback signer. Callback signers let you define signing logic, for example where to load keys from.
12+
13+
The `examples/sign_info.py` script shows how to sign an asset with a C2PA manifest and verify it using a "default" signer created with the needed signer information.
1114
These statements create a `builder` object with the specified manifest JSON (omitted in the snippet below), call `builder.sign()` to sign and attach the manifest to the source file, `tests/fixtures/C.jpg`, and save the signed asset to the output file, `output/C_signed.jpg`:
1215

1316
```py
@@ -77,7 +80,17 @@ Run the "do not train" assertion example:
7780
python examples/training.py
7881
```
7982

80-
Run the signing and verification example:
83+
### Run the signing and verification example
84+
85+
In this example, `SignerInfo` creates a `Signer` object that signs the manifest.
86+
87+
```bash
88+
python examples/sign_info.py
89+
```
90+
91+
### Run the callback signing and verification example
92+
93+
In this example, a callback signer is the signer:
8194

8295
```bash
8396
python examples/sign.py

examples/sign.py

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,79 +11,110 @@
1111
# each license.
1212

1313
# This example shows how to sign an image with a C2PA manifest
14-
# and read the metadata added to the image.
14+
# using a callback signer and read the metadata added to the image.
1515

1616
import os
1717
import c2pa
18+
from cryptography.hazmat.primitives import hashes, serialization
19+
from cryptography.hazmat.primitives.asymmetric import ec
20+
from cryptography.hazmat.backends import default_backend
21+
22+
# Note: Builder, Reader, and Signer support being used as context managers
23+
# (with 'with' statements), but this example shows manual usage which requires
24+
# explicitly calling the close() function to clean up resources.
1825

1926
fixtures_dir = os.path.join(os.path.dirname(__file__), "../tests/fixtures/")
2027
output_dir = os.path.join(os.path.dirname(__file__), "../output/")
2128

22-
# ensure the output directory exists
29+
# Ensure the output directory exists
2330
if not os.path.exists(output_dir):
2431
os.makedirs(output_dir)
2532

2633
print("c2pa version:")
2734
version = c2pa.sdk_version()
2835
print(version)
2936

30-
# Read existing C2PA metadata from the file
31-
print("\nReading existing C2PA metadata:")
32-
with open(fixtures_dir + "C.jpg", "rb") as file:
33-
reader = c2pa.Reader("image/jpeg", file)
34-
print(reader.json())
3537

36-
# Create a signer from certificate and key files
38+
# Load certificates and private key (here from the test fixtures)
39+
# This is OK for development, but in production you should use a
40+
# secure way to load the certificates and private key.
3741
certs = open(fixtures_dir + "es256_certs.pem", "rb").read()
3842
key = open(fixtures_dir + "es256_private.key", "rb").read()
3943

40-
signer_info = c2pa.C2paSignerInfo(
41-
alg=b"es256", # Use bytes instead of encoded string
42-
sign_cert=certs,
43-
private_key=key,
44-
ta_url=b"http://timestamp.digicert.com" # Use bytes and add timestamp URL
45-
)
44+
# Define a callback signer function
45+
def callback_signer_es256(data: bytes) -> bytes:
46+
"""Callback function that signs data using ES256 algorithm."""
47+
private_key = serialization.load_pem_private_key(
48+
key,
49+
password=None,
50+
backend=default_backend()
51+
)
52+
signature = private_key.sign(
53+
data,
54+
ec.ECDSA(hashes.SHA256())
55+
)
56+
return signature
4657

47-
signer = c2pa.Signer.from_info(signer_info)
58+
# Create a signer using the callback function we defined
59+
signer = c2pa.Signer.from_callback(
60+
callback=callback_signer_es256,
61+
alg=c2pa.C2paSigningAlg.ES256,
62+
certs=certs.decode('utf-8'),
63+
tsa_url="http://timestamp.digicert.com"
64+
)
4865

4966
# Create a manifest definition as a dictionary
67+
# This manifest follows the V2 manifest format
5068
manifest_definition = {
5169
"claim_generator": "python_example",
5270
"claim_generator_info": [{
5371
"name": "python_example",
5472
"version": "0.0.1",
5573
}],
74+
"claim_version": 2,
5675
"format": "image/jpeg",
5776
"title": "Python Example Image",
5877
"ingredients": [],
5978
"assertions": [
6079
{
61-
'label': 'stds.schema-org.CreativeWork',
62-
'data': {
63-
'@context': 'http://schema.org/',
64-
'@type': 'CreativeWork',
65-
'author': [
66-
{'@type': 'Person', 'name': 'Example User'}
80+
"label": "c2pa.actions",
81+
"data": {
82+
"actions": [
83+
{
84+
"action": "c2pa.created",
85+
"parameters": {
86+
# could hold additional information about this step
87+
# eg. model used, etc.
88+
}
89+
}
6790
]
68-
},
69-
'kind': 'Json'
91+
}
7092
}
7193
]
7294
}
7395

96+
# Create the builder with the manifest definition
7497
builder = c2pa.Builder(manifest_definition)
7598

76-
# Sign the image
77-
print("\nSigning the image...")
78-
with open(fixtures_dir + "C.jpg", "rb") as source:
79-
with open(output_dir + "C_signed.jpg", "wb") as dest:
80-
result = builder.sign(signer, "image/jpeg", source, dest)
99+
# Sign the image with the signer created above,
100+
# which will use the callback signer
101+
print("\nSigning the image file...")
102+
builder.sign_file(
103+
source_path=fixtures_dir + "A.jpg",
104+
dest_path=output_dir + "A_signed.jpg",
105+
signer=signer
106+
)
107+
108+
# Clean up
109+
signer.close()
110+
builder.close()
81111

82-
# Read the signed image to verify
112+
# Re-Read the signed image to verify
83113
print("\nReading signed image metadata:")
84-
with open(output_dir + "C_signed.jpg", "rb") as file:
114+
with open(output_dir + "A_signed.jpg", "rb") as file:
85115
reader = c2pa.Reader("image/jpeg", file)
86116
print(reader.json())
117+
reader.close()
87118

88119
print("\nExample completed successfully!")
89120

examples/sign_info.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Copyright 2025 Adobe. All rights reserved.
2+
# This file is licensed to you under the Apache License,
3+
# Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
4+
# or the MIT license (http://opensource.org/licenses/MIT),
5+
# at your option.
6+
# Unless required by applicable law or agreed to in writing,
7+
# this software is distributed on an "AS IS" BASIS, WITHOUT
8+
# WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
9+
# implied. See the LICENSE-MIT and LICENSE-APACHE files for the
10+
# specific language governing permissions and limitations under
11+
# each license.
12+
13+
###############################################################
14+
# This example shows an "older" way of signing,
15+
# and is left here as reference.
16+
# Please refer to sign.py for the recommended implementation.
17+
###############################################################
18+
19+
# This example shows how to sign an image with a C2PA manifest
20+
# and read the metadata added to the image.
21+
22+
import os
23+
import c2pa
24+
25+
fixtures_dir = os.path.join(os.path.dirname(__file__), "../tests/fixtures/")
26+
output_dir = os.path.join(os.path.dirname(__file__), "../output/")
27+
28+
# Note: Builder, Reader, and Signer support being used as context managers
29+
# (with 'with' statements), but this example shows manual usage which requires
30+
# explicitly calling the close() function to clean up resources.
31+
32+
# Ensure the output directory exists
33+
if not os.path.exists(output_dir):
34+
os.makedirs(output_dir)
35+
36+
print("c2pa version:")
37+
version = c2pa.sdk_version()
38+
print(version)
39+
40+
# Read existing C2PA metadata from the file
41+
print("\nReading existing C2PA metadata:")
42+
with open(fixtures_dir + "C.jpg", "rb") as file:
43+
reader = c2pa.Reader("image/jpeg", file)
44+
print(reader.json())
45+
reader.close()
46+
47+
# Create a signer from certificate and key files
48+
certs = open(fixtures_dir + "es256_certs.pem", "rb").read()
49+
key = open(fixtures_dir + "es256_private.key", "rb").read()
50+
51+
# Define Signer information
52+
signer_info = c2pa.C2paSignerInfo(
53+
alg=b"es256", # Use bytes instead of encoded string
54+
sign_cert=certs,
55+
private_key=key,
56+
ta_url=b"http://timestamp.digicert.com" # Use bytes and add timestamp URL
57+
)
58+
59+
# Create the Signer from the information
60+
signer = c2pa.Signer.from_info(signer_info)
61+
62+
# Create a manifest definition as a dictionary
63+
# This examples signs using a V1 manifest
64+
manifest_definition = {
65+
"claim_generator": "python_example",
66+
"claim_generator_info": [{
67+
"name": "python_example",
68+
"version": "0.0.1",
69+
}],
70+
"format": "image/jpeg",
71+
"title": "Python Example Image",
72+
"ingredients": [],
73+
"assertions": [
74+
{
75+
"label": "c2pa.actions",
76+
"data": {
77+
"actions": [
78+
{
79+
"action": "c2pa.created",
80+
"parameters": {
81+
# could hold additional information about this step
82+
}
83+
}
84+
]
85+
}
86+
}
87+
]
88+
}
89+
90+
# Create the builder with the manifest definition
91+
builder = c2pa.Builder(manifest_definition)
92+
93+
# Sign the image
94+
print("\nSigning the image...")
95+
with open(fixtures_dir + "C.jpg", "rb") as source:
96+
with open(output_dir + "C_signed.jpg", "wb") as dest:
97+
result = builder.sign(signer, "image/jpeg", source, dest)
98+
99+
# Read the signed image to verify
100+
print("\nReading signed image metadata:")
101+
with open(output_dir + "C_signed.jpg", "rb") as file:
102+
reader = c2pa.Reader("image/jpeg", file)
103+
print(reader.json())
104+
reader.close()
105+
106+
# Clean up resources manually, since we are not using with statements
107+
signer.close()
108+
builder.close()
109+
110+
print("\nExample completed successfully!")
111+

examples/training.py

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,7 @@
4242
def getitem(d, key):
4343
return reduce(operator.getitem, key, d)
4444

45-
46-
# This function signs data with PS256 using a private key
47-
def sign_ps256(data: bytes, key: bytes) -> bytes:
48-
private_key = serialization.load_pem_private_key(
49-
key,
50-
password=None,
51-
)
52-
signature = private_key.sign(
53-
data,
54-
padding.PSS(
55-
mgf=padding.MGF1(hashes.SHA256()),
56-
salt_length=padding.PSS.MAX_LENGTH
57-
),
58-
hashes.SHA256()
59-
)
60-
return signature
61-
6245
# First create an asset with a do not train assertion
63-
6446
# Define a manifest with the do not train assertion
6547
manifest_json = {
6648
"claim_generator_info": [{
@@ -72,19 +54,19 @@ def sign_ps256(data: bytes, key: bytes) -> bytes:
7254
"format": "image/jpeg",
7355
"identifier": "thumbnail"
7456
},
75-
"assertions": [
76-
{
77-
"label": "c2pa.training-mining",
78-
"data": {
79-
"entries": {
80-
"c2pa.ai_generative_training": { "use": "notAllowed" },
81-
"c2pa.ai_inference": { "use": "notAllowed" },
82-
"c2pa.ai_training": { "use": "notAllowed" },
83-
"c2pa.data_mining": { "use": "notAllowed" }
57+
"assertions": [{
58+
"label": "cawg.training-mining",
59+
"data": {
60+
"entries": {
61+
"cawg.ai_inference": {
62+
"use": "notAllowed"
63+
},
64+
"cawg.ai_generative_training": {
65+
"use": "notAllowed"
66+
}
67+
}
8468
}
85-
}
86-
}
87-
]
69+
}]
8870
}
8971

9072
ingredient_json = {
@@ -145,9 +127,10 @@ def sign_ps256(data: bytes, key: bytes) -> bytes:
145127
manifest_store = json.loads(reader.json())
146128

147129
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]
130+
148131
for assertion in manifest["assertions"]:
149-
if assertion["label"] == "c2pa.training-mining":
150-
if getitem(assertion, ("data","entries","c2pa.ai_training","use")) == "notAllowed":
132+
if assertion["label"] == "cawg.training-mining":
133+
if getitem(assertion, ("data","entries","cawg.ai_generative_training","use")) == "notAllowed":
151134
allowed = False
152135

153136
# get the ingredient thumbnail and save it to a file using resource_to_stream

0 commit comments

Comments
 (0)