Skip to content

Commit 5a32e52

Browse files
authored
Merge pull request #91 from contentauth/docs/rel-notes
docs: Update release notes with breaking change, other doc updates
2 parents 6a562e8 + cd473d5 commit 5a32e52

File tree

2 files changed

+90
-103
lines changed

2 files changed

+90
-103
lines changed

docs/release-notes.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
See [Release tag 0.6.0](https://github.com/contentauth/c2pa-python/releases/tag/v0.6.0).
88

9+
### Breaking changes
10+
11+
The signature of the `c2pa.sign_ps256()` method changed. Previously, the argument was a file path but now its the PEM certificate string.
12+
913
## Version 0.5.2
1014

1115
New features:

docs/usage.md

Lines changed: 86 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,18 @@ manifest_json = json.dumps({
3636

3737
## Signing function
3838

39-
The `sign_ps256` function is [defined in the library](https://github.com/contentauth/c2pa-python/blob/main/c2pa/c2pa_api/c2pa_api.py#L209) is used in both file-based and stream-based methods and is reproduced here to show how signing is performed.
39+
The `sign_ps256` function is [defined in the library](https://github.com/contentauth/c2pa-python/blob/main/c2pa/c2pa_api/c2pa_api.py#L244) and used in both file-based and stream-based methods. It's reproduced here to show how signing is performed.
4040

4141
```py
4242
# Example of using Python crypto to sign data using openssl with Ps256
4343
from cryptography.hazmat.primitives import hashes, serialization
4444
from cryptography.hazmat.primitives.asymmetric import padding
4545

46-
def sign_ps256(data: bytes, key_path: str) -> bytes:
47-
with open(key_path, "rb") as key_file:
48-
private_key = serialization.load_pem_private_key(
49-
key_file.read(),
50-
password=None,
51-
)
46+
def sign_ps256(data: bytes, key: bytes) -> bytes:
47+
private_key = serialization.load_pem_private_key(
48+
key,
49+
password=None,
50+
)
5251
signature = private_key.sign(
5352
data,
5453
padding.PSS(
@@ -78,7 +77,7 @@ try:
7877
reader = c2pa.Reader.from_file("path/to/media_file.jpg")
7978

8079
# Print the JSON for a manifest.
81-
print("manifest store:", reader.json())
80+
print("Manifest store:", reader.json())
8281

8382
# Get the active manifest.
8483
manifest = reader.get_active_manifest()
@@ -99,52 +98,51 @@ except Exception as err:
9998
Use a `Builder` to add a manifest to an asset:
10099

101100
```py
102-
try:
103-
# Define a function to sign the claim bytes
104-
# In this case we are using a pre-defined sign_ps256 method, passing in our private cert
105-
# Normally this cert would be kept safe in some other location
106-
def private_sign(data: bytes) -> bytes:
107-
return sign_ps256(data, "tests/fixtures/ps256.pem")
108-
109-
# read our public certs into memory
110-
certs = open(data_dir + "ps256.pub", "rb").read()
111-
112-
# Create a signer from the private signer, certs and a time stamp service url
113-
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
114-
115-
# Create a builder add a thumbnail resource and an ingredient file.
116-
builder = Builder(manifest_json)
117-
118-
# The uri provided here "thumbnail" must match an identifier in the manifest definition.
119-
builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg")
120-
121-
# Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
122-
ingredient_json = {
123-
"title": "A.jpg",
124-
"relationship": "parentOf", # "parentOf", "componentOf" or "inputTo"
125-
"thumbnail": {
126-
"identifier": "thumbnail",
127-
"format": "image/jpeg"
128-
}
129-
}
130-
131-
# Add the ingredient to the builder loading information from a source file.
132-
builder.add_ingredient_file(ingredient_json, "tests/fixtures/A.jpg")
133-
134-
# At this point we could archive or unarchive our Builder to continue later.
135-
# In this example we use a bytearray for the archive stream.
136-
# all ingredients and resources will be saved in the archive
137-
archive = io.BytesIO(bytearray())
138-
builder.to_archive(archive)
139-
archive.seek()
140-
builder = builder.from_archive(archive)
141-
142-
# Sign and add our manifest to a source file, writing it to an output file.
143-
# This returns the binary manifest data that could be uploaded to cloud storage.
144-
c2pa_data = builder.sign_file(signer, "tests/fixtures/A.jpg", "target/out.jpg")
145-
146-
except Exception as err:
147-
print(err)
101+
def test_v2_sign(self):
102+
# Define source folder for any assets being read.
103+
data_dir = "tests/fixtures/"
104+
try:
105+
key = open(data_dir + "ps256.pem", "rb").read()
106+
def sign(data: bytes) -> bytes:
107+
return sign_ps256(data, key)
108+
109+
certs = open(data_dir + "ps256.pub", "rb").read()
110+
# Create a local signer from a certificate pem file.
111+
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
112+
113+
builder = Builder(manifest_def)
114+
115+
builder.add_ingredient_file(ingredient_def, data_dir + "A.jpg")
116+
117+
builder.add_resource_file("A.jpg", data_dir + "A.jpg")
118+
119+
builder.to_archive(open("target/archive.zip", "wb"))
120+
121+
builder = Builder.from_archive(open("target/archive.zip", "rb"))
122+
123+
with tempfile.TemporaryDirectory() as output_dir:
124+
output_path = output_dir + "out.jpg"
125+
if os.path.exists(output_path):
126+
os.remove(output_path)
127+
c2pa_data = builder.sign_file(signer, data_dir + "A.jpg", output_dir + "out.jpg")
128+
assert len(c2pa_data) > 0
129+
130+
reader = Reader.from_file(output_dir + "out.jpg")
131+
print(reader.json())
132+
manifest_store = json.loads(reader.json())
133+
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]
134+
assert "python_test" in manifest["claim_generator"]
135+
# Check custom title and format.
136+
assert manifest["title"]== "My Title"
137+
assert manifest,["format"] == "image/jpeg"
138+
# There should be no validation status errors.
139+
assert manifest.get("validation_status") == None
140+
assert manifest["ingredients"][0]["relationship"] == "parentOf"
141+
assert manifest["ingredients"][0]["title"] == "A.jpg"
142+
143+
except Exception as e:
144+
print("Failed to sign manifest store: " + str(e))
145+
exit(1)
148146
```
149147

150148
## Stream-based operation
@@ -182,54 +180,39 @@ except Exception as err:
182180
Use a `Builder` to add a manifest to an asset:
183181

184182
```py
183+
from c2pa import Builder, Error, Reader, SigningAlg, create_signer, sdk_version, sign_ps256, version
184+
...
185+
data_dir = "tests/fixtures/"
185186
try:
186-
# Define a function to sign the claim bytes
187-
# In this case we are using a pre-defined sign_ps256 method, passing in our private cert
188-
# Normally this cert would be kept safe in some other location
189-
def private_sign(data: bytes) -> bytes:
190-
return sign_ps256(data, "tests/fixtures/ps256.pem")
191-
192-
# read our public certs into memory
193-
certs = open(data_dir + "ps256.pub", "rb").read()
194-
195-
# Create a signer from the private signer, certs and a time stamp service url
196-
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
197-
198-
# Create a builder add a thumbnail resource and an ingredient file.
199-
builder = Builder(manifest_json)
200-
201-
# Add the resource from a stream
202-
a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb")
203-
builder.add_resource("image/jpeg", a_thumbnail_jpg_stream)
204-
205-
# Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
206-
ingredient_json = {
207-
"title": "A.jpg",
208-
"relationship": "parentOf", # "parentOf", "componentOf" or "inputTo"
209-
"thumbnail": {
210-
"identifier": "thumbnail",
211-
"format": "image/jpeg"
212-
}
213-
}
214-
215-
# Add the ingredient from a stream
216-
a_jpg_stream = open("tests/fixtures/A.jpg", "rb")
217-
builder.add_ingredient("image/jpeg", a_jpg_stream)
218-
219-
# At this point we could archive or unarchive our Builder to continue later.
220-
# In this example we use a bytearray for the archive stream.
221-
# all ingredients and resources will be saved in the archive
222-
archive = io.BytesIO(bytearray())
223-
builder.to_archive(archive)
224-
archive.seek()
225-
builder = builder.from_archive(archive)
226-
227-
# Sign the builder with a stream and output it to a stream
228-
# This returns the binary manifest data that could be uploaded to cloud storage.
229-
input_stream = open("tests/fixtures/A.jpg", "rb")
230-
output_stream = open("target/out.jpg", "wb")
231-
c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream)
232-
233-
except Exception as err:
234-
print(err)
187+
key = open(data_dir + "ps256.pem", "rb").read()
188+
def sign(data: bytes) -> bytes:
189+
return sign_ps256(data, key)
190+
191+
certs = open(data_dir + "ps256.pub", "rb").read()
192+
# Create a local signer from a certificate pem file
193+
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
194+
195+
builder = Builder(manifest_def)
196+
197+
builder.add_ingredient_file(ingredient_def, data_dir + "A.jpg")
198+
builder.add_resource_file("A.jpg", data_dir + "A.jpg")
199+
builder.to_archive(open("target/archive.zip", "wb"))
200+
201+
builder = Builder.from_archive(open("target/archive.zip", "rb"))
202+
203+
with tempfile.TemporaryDirectory() as output_dir:
204+
output_path = output_dir + "out.jpg"
205+
if os.path.exists(output_path):
206+
os.remove(output_path)
207+
c2pa_data = builder.sign_file(signer, data_dir + "A.jpg", output_dir + "out.jpg")
208+
assert len(c2pa_data) > 0
209+
210+
reader = Reader.from_file(output_dir + "out.jpg")
211+
print(reader.json())
212+
manifest_store = json.loads(reader.json())
213+
manifest = manifest_store["manifests"][manifest_store["active_manifest"]]
214+
215+
except Exception as e:
216+
print("Failed to sign manifest store: " + str(e))
217+
exit(1)
235218
```

0 commit comments

Comments
 (0)