Skip to content
Merged
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
15 changes: 14 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ The examples use asset files from the `tests/fixtures` directory, save the resul
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.


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.

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.
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`:

```py
Expand Down Expand Up @@ -77,7 +80,17 @@ Run the "do not train" assertion example:
python examples/training.py
```

Run the signing and verification example:
### Run the signing and verification example

In this example, `SignerInfo` creates a `Signer` object that signs the manifest.

```bash
python examples/sign_info.py
```

### Run the callback signing and verification example

In this example, a callback signer is the signer:

```bash
python examples/sign.py
Expand Down
91 changes: 61 additions & 30 deletions examples/sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,79 +11,110 @@
# each license.

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

import os
import c2pa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend

# Note: Builder, Reader, and Signer support being used as context managers
# (with 'with' statements), but this example shows manual usage which requires
# explicitly calling the close() function to clean up resources.

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

# ensure the output directory exists
# Ensure the output directory exists
if not os.path.exists(output_dir):
os.makedirs(output_dir)

print("c2pa version:")
version = c2pa.sdk_version()
print(version)

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

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

signer_info = c2pa.C2paSignerInfo(
alg=b"es256", # Use bytes instead of encoded string
sign_cert=certs,
private_key=key,
ta_url=b"http://timestamp.digicert.com" # Use bytes and add timestamp URL
)
# Define a callback signer function
def callback_signer_es256(data: bytes) -> bytes:
"""Callback function that signs data using ES256 algorithm."""
private_key = serialization.load_pem_private_key(
key,
password=None,
backend=default_backend()
)
signature = private_key.sign(
data,
ec.ECDSA(hashes.SHA256())
)
return signature

signer = c2pa.Signer.from_info(signer_info)
# Create a signer using the callback function we defined
signer = c2pa.Signer.from_callback(
callback=callback_signer_es256,
alg=c2pa.C2paSigningAlg.ES256,
certs=certs.decode('utf-8'),
tsa_url="http://timestamp.digicert.com"
)

# Create a manifest definition as a dictionary
# This manifest follows the V2 manifest format
manifest_definition = {
"claim_generator": "python_example",
"claim_generator_info": [{
"name": "python_example",
"version": "0.0.1",
}],
"claim_version": 2,
"format": "image/jpeg",
"title": "Python Example Image",
"ingredients": [],
"assertions": [
{
'label': 'stds.schema-org.CreativeWork',
'data': {
'@context': 'http://schema.org/',
'@type': 'CreativeWork',
'author': [
{'@type': 'Person', 'name': 'Example User'}
"label": "c2pa.actions",
"data": {
"actions": [
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I believe this is the most minimal valid V2 manifest we can have.

{
"action": "c2pa.created",
"parameters": {
# could hold additional information about this step
# eg. model used, etc.
}
}
]
},
'kind': 'Json'
}
}
]
}

# Create the builder with the manifest definition
builder = c2pa.Builder(manifest_definition)

# Sign the image
print("\nSigning the image...")
with open(fixtures_dir + "C.jpg", "rb") as source:
with open(output_dir + "C_signed.jpg", "wb") as dest:
result = builder.sign(signer, "image/jpeg", source, dest)
# Sign the image with the signer created above,
# which will use the callback signer
print("\nSigning the image file...")
builder.sign_file(
source_path=fixtures_dir + "A.jpg",
dest_path=output_dir + "A_signed.jpg",
signer=signer
)

# Clean up
signer.close()
builder.close()

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

print("\nExample completed successfully!")

111 changes: 111 additions & 0 deletions examples/sign_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Copyright 2025 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.

###############################################################
# This example shows an "older" way of signing,
# and is left here as reference.
# Please refer to sign.py for the recommended implementation.
###############################################################

# This example shows how to sign an image with a C2PA manifest
# and read the metadata added to the image.

import os
import c2pa

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

# Note: Builder, Reader, and Signer support being used as context managers
# (with 'with' statements), but this example shows manual usage which requires
# explicitly calling the close() function to clean up resources.

# Ensure the output directory exists
if not os.path.exists(output_dir):
os.makedirs(output_dir)

print("c2pa version:")
version = c2pa.sdk_version()
print(version)

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

# Create a signer from certificate and key files
certs = open(fixtures_dir + "es256_certs.pem", "rb").read()
key = open(fixtures_dir + "es256_private.key", "rb").read()

# Define Signer information
signer_info = c2pa.C2paSignerInfo(
alg=b"es256", # Use bytes instead of encoded string
sign_cert=certs,
private_key=key,
ta_url=b"http://timestamp.digicert.com" # Use bytes and add timestamp URL
)

# Create the Signer from the information
signer = c2pa.Signer.from_info(signer_info)

# Create a manifest definition as a dictionary
# This examples signs using a V1 manifest
manifest_definition = {
"claim_generator": "python_example",
"claim_generator_info": [{
"name": "python_example",
"version": "0.0.1",
}],
"format": "image/jpeg",
"title": "Python Example Image",
"ingredients": [],
"assertions": [
{
"label": "c2pa.actions",
"data": {
"actions": [
{
"action": "c2pa.created",
"parameters": {
# could hold additional information about this step
}
}
]
}
}
]
}

# Create the builder with the manifest definition
builder = c2pa.Builder(manifest_definition)

# Sign the image
print("\nSigning the image...")
with open(fixtures_dir + "C.jpg", "rb") as source:
with open(output_dir + "C_signed.jpg", "wb") as dest:
result = builder.sign(signer, "image/jpeg", source, dest)

# Read the signed image to verify
print("\nReading signed image metadata:")
with open(output_dir + "C_signed.jpg", "rb") as file:
reader = c2pa.Reader("image/jpeg", file)
print(reader.json())
reader.close()

# Clean up resources manually, since we are not using with statements
signer.close()
builder.close()

print("\nExample completed successfully!")

47 changes: 15 additions & 32 deletions examples/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,7 @@
def getitem(d, key):
return reduce(operator.getitem, key, d)


# This function signs data with PS256 using a private key
def sign_ps256(data: bytes, key: bytes) -> bytes:
private_key = serialization.load_pem_private_key(
key,
password=None,
)
signature = private_key.sign(
data,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return signature

# First create an asset with a do not train assertion

# Define a manifest with the do not train assertion
manifest_json = {
"claim_generator_info": [{
Expand All @@ -72,19 +54,19 @@ def sign_ps256(data: bytes, key: bytes) -> bytes:
"format": "image/jpeg",
"identifier": "thumbnail"
},
"assertions": [
{
"label": "c2pa.training-mining",
"data": {
"entries": {
"c2pa.ai_generative_training": { "use": "notAllowed" },
"c2pa.ai_inference": { "use": "notAllowed" },
"c2pa.ai_training": { "use": "notAllowed" },
"c2pa.data_mining": { "use": "notAllowed" }
"assertions": [{
"label": "cawg.training-mining",
"data": {
"entries": {
"cawg.ai_inference": {
"use": "notAllowed"
},
"cawg.ai_generative_training": {
"use": "notAllowed"
}
}
}
}
}
]
}]
}

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

manifest = manifest_store["manifests"][manifest_store["active_manifest"]]

for assertion in manifest["assertions"]:
if assertion["label"] == "c2pa.training-mining":
if getitem(assertion, ("data","entries","c2pa.ai_training","use")) == "notAllowed":
if assertion["label"] == "cawg.training-mining":
if getitem(assertion, ("data","entries","cawg.ai_generative_training","use")) == "notAllowed":
allowed = False

# get the ingredient thumbnail and save it to a file using resource_to_stream
Expand Down
Loading
Loading