Skip to content

Commit 13b0717

Browse files
authored
Merge pull request #40 from contentauth/docs/signing-example
Add example of Python signing code
2 parents 30bf84a + d0029c8 commit 13b0717

File tree

1 file changed

+141
-68
lines changed

1 file changed

+141
-68
lines changed

README.md

Lines changed: 141 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,72 @@ Import the API as follows:
4646
from c2pa import *
4747
```
4848

49-
### Read and validate C2PA data in a file or stream
49+
### Define manifest JSON
5050

51-
Use the `Reader` to read C2PA data from the specified file.
52-
This examines the specified media file for C2PA data and generates a report of any data it finds. If there are validation errors, the report includes a `validation_status` field. For a summary of supported media types, see [Supported file formats](#supported-file-formats).
51+
The Python library works with both file-based and stream-based operations.
52+
In both cases, the manifest JSON string defines the C2PA manifest to add to an asset; for example:
5353

54-
A media file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map.
54+
```py
55+
manifest_json = json.dumps({
56+
"claim_generator": "python_test/0.1",
57+
"assertions": [
58+
{
59+
"label": "c2pa.training-mining",
60+
"data": {
61+
"entries": {
62+
"c2pa.ai_generative_training": { "use": "notAllowed" },
63+
"c2pa.ai_inference": { "use": "notAllowed" },
64+
"c2pa.ai_training": { "use": "notAllowed" },
65+
"c2pa.data_mining": { "use": "notAllowed" }
66+
}
67+
}
68+
}
69+
]
70+
})
71+
```
72+
73+
### Signing function
74+
75+
The `sign_ps256` function is [defined in the library](https://github.com/contentauth/c2pa-python/blob/main/c2pa/c2pa_api/c2pa_api.py#L209) and is reproduced here to show how signing is performed.
76+
77+
```py
78+
# Example of using Python crypto to sign data using openssl with Ps256
79+
from cryptography.hazmat.primitives import hashes, serialization
80+
from cryptography.hazmat.primitives.asymmetric import padding
81+
82+
def sign_ps256(data: bytes, key_path: str) -> bytes:
83+
with open(key_path, "rb") as key_file:
84+
private_key = serialization.load_pem_private_key(
85+
key_file.read(),
86+
password=None,
87+
)
88+
signature = private_key.sign(
89+
data,
90+
padding.PSS(
91+
mgf=padding.MGF1(hashes.SHA256()),
92+
salt_length=padding.PSS.MAX_LENGTH
93+
),
94+
hashes.SHA256()
95+
)
96+
return signature
97+
```
98+
99+
### File-based operation
100+
101+
**Read and validate C2PA data from an asset file**
102+
103+
Use the `Reader` to read C2PA data from the specified asset file (see [supported file formats](#supported-file-formats)).
55104

56-
The manifests may contain binary resources such as thumbnails which can be retrieved with `resource_to_stream` or `resource_to_file` using the associated `identifier` field values and a `uri`.
105+
This examines the specified media file for C2PA data and generates a report of any data it finds. If there are validation errors, the report includes a `validation_status` field.
106+
107+
An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. The manifests may contain binary resources such as thumbnails which can be retrieved with `resource_to_stream` or `resource_to_file` using the associated `identifier` field values and a `uri`.
57108

58109
NOTE: For a comprehensive reference to the JSON manifest structure, see the [Manifest store reference](https://opensource.contentauthenticity.org/docs/manifest/manifest-ref).
110+
59111
```py
60112
try:
61113
# Create a reader from a file path
62114
reader = c2pa.Reader.from_file("path/to/media_file.jpg")
63-
# It's also possible to create a reader from a format and stream
64-
# Note that these two readers are functionally equivalent
65-
stream = open("path/to/media_file.jpg", "rb")
66-
reader = c2pa.Reader("image/jpeg", stream)
67115

68116
# Print the JSON for a manifest.
69117
print("manifest store:", reader.json())
@@ -80,9 +128,9 @@ except Exception as err:
80128
print(err)
81129
```
82130

83-
### Add a signed manifest to a media file or stream
131+
**Add a signed manifest to an asset file**
84132

85-
**WARNING**: This example accesses the private key and security certficate directly from the local file system. This is fine during development, but doing so in production may be insecure. Instead use a Key Management Service (KMS) or a hardware security module (HSM) to access the certificate and key; for example as show in the [C2PA Python Example](https://github.com/contentauth/c2pa-python-example).
133+
**WARNING**: This example accesses the private key and security certificate directly from the local file system. This is fine during development, but doing so in production may be insecure. Instead use a Key Management Service (KMS) or a hardware security module (HSM) to access the certificate and key; for example as show in the [C2PA Python Example](https://github.com/contentauth/c2pa-python-example).
86134

87135
Use a `Builder` to add a manifest to an asset:
88136

@@ -100,42 +148,12 @@ try:
100148
# Create a signer from the private signer, certs and a time stamp service url
101149
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
102150

103-
# Define a manifest with thumbnail and an assertion.
104-
manifest_json = {
105-
"claim_generator_info": [{
106-
"name": "python_test",
107-
"version": "0.1"
108-
}],
109-
"title": "Do Not Train Example",
110-
"thumbnail": {
111-
"format": "image/jpeg",
112-
"identifier": "thumbnail"
113-
},
114-
"assertions": [
115-
{
116-
"label": "c2pa.training-mining",
117-
"data": {
118-
"entries": {
119-
"c2pa.ai_generative_training": { "use": "notAllowed" },
120-
"c2pa.ai_inference": { "use": "notAllowed" },
121-
"c2pa.ai_training": { "use": "notAllowed" },
122-
"c2pa.data_mining": { "use": "notAllowed" }
123-
}
124-
}
125-
}
126-
]
127-
}
128-
129151
# Create a builder add a thumbnail resource and an ingredient file.
130152
builder = Builder(manifest_json)
131153

132154
# The uri provided here "thumbnail" must match an identifier in the manifest definition.
133155
builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg")
134156

135-
# Or add the resource from a stream
136-
a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb")
137-
builder.add_resource("image/jpeg", a_thumbnail_jpg_stream)
138-
139157
# Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
140158
ingredient_json = {
141159
"title": "A.jpg",
@@ -149,10 +167,6 @@ try:
149167
# Add the ingredient to the builder loading information from a source file.
150168
builder.add_ingredient_file(ingredient_json, "tests/fixtures/A.jpg")
151169

152-
# Or add the ingredient from a stream
153-
a_jpg_stream = open("tests/fixtures/A.jpg", "rb")
154-
builder.add_ingredient("image/jpeg", a_jpg_stream)
155-
156170
# At this point we could archive or unarchive our Builder to continue later.
157171
# In this example we use a bytearray for the archive stream.
158172
# all ingredients and resources will be saved in the archive
@@ -165,37 +179,96 @@ try:
165179
# This returns the binary manifest data that could be uploaded to cloud storage.
166180
c2pa_data = builder.sign_file(signer, "tests/fixtures/A.jpg", "target/out.jpg")
167181

168-
# Or sign the builder with a stream and output it to a stream
169-
input_stream = open("tests/fixtures/A.jpg", "rb")
170-
output_stream = open("target/out.jpg", "wb")
171-
c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream)
182+
except Exception as err:
183+
print(err)
184+
```
185+
186+
### Stream-based operation
187+
188+
Instead of working with files, you can read, validate, and add a signed manifest to streamed data. This example code does the same thing as the file-based example.
189+
190+
**Read and validate C2PA data from a stream**
191+
192+
```py
193+
try:
194+
# It's also possible to create a reader from a format and stream
195+
# Note that these two readers are functionally equivalent
196+
stream = open("path/to/media_file.jpg", "rb")
197+
reader = c2pa.Reader("image/jpeg", stream)
198+
199+
# Print the JSON for a manifest.
200+
print("manifest store:", reader.json())
201+
202+
# Get the active manifest.
203+
manifest = reader.get_active_manifest()
204+
if manifest != None:
205+
206+
# get the uri to the manifest's thumbnail and write it to a file
207+
uri = manifest["thumbnail"]["identifier"]
208+
reader.resource_to_file(uri, "thumbnail_v2.jpg")
172209

173210
except Exception as err:
174211
print(err)
175-
```
212+
```
176213

177-
### Creating a manifest JSON definition file
214+
**Add a signed manifest to a stream**
178215

179-
The manifest JSON string defines the C2PA manifest to add to the file.
216+
**WARNING**: This example accesses the private key and security certificate directly from the local file system. This is fine during development, but doing so in production may be insecure. Instead use a Key Management Service (KMS) or a hardware security module (HSM) to access the certificate and key; for example as show in the [C2PA Python Example](https://github.com/contentauth/c2pa-python-example).
217+
218+
Use a `Builder` to add a manifest to an asset:
180219

181220
```py
182-
manifest_json = json.dumps({
183-
"claim_generator": "python_test/0.1",
184-
"assertions": [
185-
{
186-
"label": "c2pa.training-mining",
187-
"data": {
188-
"entries": {
189-
"c2pa.ai_generative_training": { "use": "notAllowed" },
190-
"c2pa.ai_inference": { "use": "notAllowed" },
191-
"c2pa.ai_training": { "use": "notAllowed" },
192-
"c2pa.data_mining": { "use": "notAllowed" }
193-
}
194-
}
221+
try:
222+
# Define a function to sign the claim bytes
223+
# In this case we are using a pre-defined sign_ps256 method, passing in our private cert
224+
# Normally this cert would be kept safe in some other location
225+
def private_sign(data: bytes) -> bytes:
226+
return sign_ps256(data, "tests/fixtures/ps256.pem")
227+
228+
# read our public certs into memory
229+
certs = open(data_dir + "ps256.pub", "rb").read()
230+
231+
# Create a signer from the private signer, certs and a time stamp service url
232+
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
233+
234+
# Create a builder add a thumbnail resource and an ingredient file.
235+
builder = Builder(manifest_json)
236+
237+
# Add the resource from a stream
238+
a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb")
239+
builder.add_resource("image/jpeg", a_thumbnail_jpg_stream)
240+
241+
# Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
242+
ingredient_json = {
243+
"title": "A.jpg",
244+
"relationship": "parentOf", # "parentOf", "componentOf" or "inputTo"
245+
"thumbnail": {
246+
"identifier": "thumbnail",
247+
"format": "image/jpeg"
195248
}
196-
]
197-
})
198-
```
249+
}
250+
251+
# Add the ingredient from a stream
252+
a_jpg_stream = open("tests/fixtures/A.jpg", "rb")
253+
builder.add_ingredient("image/jpeg", a_jpg_stream)
254+
255+
# At this point we could archive or unarchive our Builder to continue later.
256+
# In this example we use a bytearray for the archive stream.
257+
# all ingredients and resources will be saved in the archive
258+
archive = io.BytesIO(bytearray())
259+
builder.to_archive(archive)
260+
archive.seek()
261+
builder = builder.from_archive(archive)
262+
263+
# Sign the builder with a stream and output it to a stream
264+
# This returns the binary manifest data that could be uploaded to cloud storage.
265+
input_stream = open("tests/fixtures/A.jpg", "rb")
266+
output_stream = open("target/out.jpg", "wb")
267+
c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream)
268+
269+
except Exception as err:
270+
print(err)
271+
```
199272

200273
## Supported file formats
201274

0 commit comments

Comments
 (0)