ruby-c2pa 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +87 -1
- data/CONTRIBUTING.md +19 -2
- data/README.md +236 -28
- data/ext/c2pa_native/Cargo.lock +898 -666
- data/ext/c2pa_native/Cargo.toml +4 -1
- data/ext/c2pa_native/src/lib.rs +276 -30
- data/lib/c2pa/config.rb +122 -0
- data/lib/c2pa/error.rb +3 -0
- data/lib/c2pa/manifest.rb +50 -14
- data/lib/c2pa/version.rb +1 -1
- data/lib/c2pa.rb +147 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 41362e47552927fd69a0bec7203dc35f59442af7eeaeb1d5f787216e08c18b79
|
|
4
|
+
data.tar.gz: 6e93a632b24a7baddf4ba4792d8a9f8043559fd5c168df1a685b8094a73f9689
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9d9de8da30c033cc5e64ea2d27609ce8be1a9b3d08c400c98f4a092c9ad85f27a985f347477ecd3b019366cf75f9c9c65d17a40f94ce1f82476b753b5dc4a85
|
|
7
|
+
data.tar.gz: d888086e87c7eec8fd09d5f8fadb4f6e5f9493d3ecc7199264c10c38bfc254aead1e380d881bbc3b9ad3c7569e30220f57764e85bebe1f98bd0f1a8b816c4e54
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,90 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] — 2026-09-12
|
|
11
|
+
|
|
12
|
+
Adds capability on top of 0.4.0. Nothing is removed and no existing call
|
|
13
|
+
changes behaviour, so this is a minor release. The one change every caller
|
|
14
|
+
will feel is that native calls no longer hold the GVL, which only makes other
|
|
15
|
+
threads faster.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Buffer signing. `C2PA.sign_buffer(data:, format:, ...)` signs bytes held in
|
|
20
|
+
memory and returns the signed bytes; `C2PA.read_buffer(data:, format: nil)`
|
|
21
|
+
reads them back. Both require a binary string and raise `ArgumentError` for
|
|
22
|
+
any other encoding rather than transcoding the asset. The verify-after-sign
|
|
23
|
+
guard applies to buffers as it does to files. Memory use is about four
|
|
24
|
+
times the asset.
|
|
25
|
+
- The native calls (`sign_file`, `sign_buffer`, `read_file`, `read_buffer`)
|
|
26
|
+
release Ruby's global VM lock while c2pa-rs runs. Other Ruby threads keep
|
|
27
|
+
running during a sign; previously they were blocked until it returned.
|
|
28
|
+
Measured alongside a busy Ruby thread, the process now uses about 1.9
|
|
29
|
+
CPU-seconds per wall-second against 1.0 before. `Thread#kill` and
|
|
30
|
+
`Timeout` take effect when the native call returns, not during it.
|
|
31
|
+
- Thumbnails. `C2PA.configure` gains `thumbnails`, `thumbnail_size`,
|
|
32
|
+
`thumbnail_format` and `thumbnail_quality`. When enabled, a thumbnail of the
|
|
33
|
+
asset is embedded in its manifest, and of each ingredient supplied as a file.
|
|
34
|
+
Off by default: c2pa-rs upscales to its long-edge setting, so at its default
|
|
35
|
+
of 1024 a 160×120 image gets a 1024×768 thumbnail ten times its own size.
|
|
36
|
+
Produced for JPEG, PNG, WebP and TIFF; other formats sign without one.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- rb-sys is now a direct dependency of the native extension, for
|
|
41
|
+
`rb_thread_call_without_gvl`, which magnus does not wrap. It resolves to the
|
|
42
|
+
same copy magnus already uses.
|
|
43
|
+
- The native extension is built with c2pa-rs's `add_thumbnails` feature, which
|
|
44
|
+
adds the `image` crate: 16 more crates, about 17 seconds on a cold compile,
|
|
45
|
+
and 1.5 MB on the compiled extension.
|
|
46
|
+
|
|
47
|
+
## [0.4.0] — 2026-09-11
|
|
48
|
+
|
|
49
|
+
Adds capability on top of 0.3.0. Nothing is removed and no existing call
|
|
50
|
+
changes behaviour, so this is a minor release.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- Provenance chaining through ingredients. `add_ingredient` takes an optional
|
|
55
|
+
`file:`. When the file carries content credentials, its manifest is embedded
|
|
56
|
+
in the signed output and the ingredient points at it, so a verifier can follow
|
|
57
|
+
the chain from your asset back through the original. The description-only
|
|
58
|
+
form is unchanged.
|
|
59
|
+
- `intent: :update` on `C2PA::Manifest`, for non-editorial changes such as
|
|
60
|
+
correcting metadata. The source file is the parent.
|
|
61
|
+
- `C2PA.configure`, for trust and verification settings. Add a private CA's
|
|
62
|
+
root as a trust anchor and its certificates validate as `Trusted`; disable
|
|
63
|
+
`remote_manifest_fetch` and `ocsp_fetch` for environments without network
|
|
64
|
+
access. Only values you set are sent, so defaults are c2pa-rs's own.
|
|
65
|
+
- Reading content credentials from PDFs. `C2PA.read` now parses a PDF and
|
|
66
|
+
returns its manifest if one is present. Signing a PDF remains impossible;
|
|
67
|
+
c2pa-rs has no PDF writer and upstream closed the request to add one.
|
|
68
|
+
- `C2PA::InvalidSettingsError`, raised by `C2PA.configure` for unusable input.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- c2pa-rs 0.90.15 → 0.90.22. The range includes security fixes: `h2` updated
|
|
73
|
+
for RUSTSEC-2026-0258, `chacha20` moved off a yanked version, hardening of
|
|
74
|
+
BMFF chunk-index handling and CAWG identity bindings. The lockfile ships in
|
|
75
|
+
the gem, so installers get these once they upgrade.
|
|
76
|
+
- The native layer uses c2pa-rs's Context API rather than the deprecated
|
|
77
|
+
`Builder::from_json` and `Reader::from_file`, which read settings from
|
|
78
|
+
thread-local state. One shared Context is reused across calls. Signing from
|
|
79
|
+
several threads concurrently is now tested.
|
|
80
|
+
- CI fails if a deprecated c2pa-rs API reappears.
|
|
81
|
+
|
|
82
|
+
### Documentation
|
|
83
|
+
|
|
84
|
+
- The certificate section no longer claims certificates must chain to a CA in
|
|
85
|
+
the C2PA trust list, or that self-signed certificates are rejected as such.
|
|
86
|
+
A private CA works; its certificates carry `signingCredential.untrusted`
|
|
87
|
+
until the root is added as an anchor. The certificate profile c2pa-rs
|
|
88
|
+
enforces is listed, including the Authority Key Identifier that
|
|
89
|
+
`openssl x509 -req` omits by default.
|
|
90
|
+
- PDF is listed as read-only, with a note on what the test suite can and
|
|
91
|
+
cannot show: nothing available can produce a C2PA-signed PDF, so reading one
|
|
92
|
+
is untested here, though the code doing it is c2pa-rs's own.
|
|
93
|
+
|
|
10
94
|
## [0.3.0] — 2026-08-25
|
|
11
95
|
|
|
12
96
|
Runs on c2pa-rs 0.90, and fixes every defect found while building a test suite
|
|
@@ -99,7 +183,9 @@ Tagged retroactively. See the v0.2.1 tag for the defects it shipped with.
|
|
|
99
183
|
|
|
100
184
|
Tagged retroactively.
|
|
101
185
|
|
|
102
|
-
[Unreleased]: https://github.com/eddorre/ruby-c2pa/compare/v0.
|
|
186
|
+
[Unreleased]: https://github.com/eddorre/ruby-c2pa/compare/v0.5.0...HEAD
|
|
187
|
+
[0.5.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.4.0...v0.5.0
|
|
188
|
+
[0.4.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.3.0...v0.4.0
|
|
103
189
|
[0.3.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.2.1...v0.3.0
|
|
104
190
|
[0.2.1]: https://github.com/eddorre/ruby-c2pa/compare/v0.2.0...v0.2.1
|
|
105
191
|
[0.2.0]: https://github.com/eddorre/ruby-c2pa/releases/tag/v0.2.0
|
data/CONTRIBUTING.md
CHANGED
|
@@ -57,9 +57,15 @@ bundle exec ruby -Ilib -Itest test/c2pa_test.rb
|
|
|
57
57
|
git checkout -- lib/c2pa/manifest.rb
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
`git checkout --` is only safe if the file has no uncommitted changes of its
|
|
61
|
+
own. Mid-change it reverts your work along with the mutation, and the suite
|
|
62
|
+
then fails for a reason that has nothing to do with the test. Copy the file
|
|
63
|
+
aside before mutating and restore from the copy, and run the unmutated suite
|
|
64
|
+
afterwards as a control. That control run is what catches a bad restore.
|
|
65
|
+
|
|
60
66
|
Then state it in the pull request:
|
|
61
67
|
|
|
62
|
-
> Verified by reversing the title in `Manifest#to_json`:
|
|
68
|
+
> Verified by reversing the title in `Manifest#to_json`: 21 failures, against 0
|
|
63
69
|
> for the unmutated suite.
|
|
64
70
|
|
|
65
71
|
A broad mutation like that trips many tests, which is fine. A narrow one that
|
|
@@ -69,7 +75,7 @@ test is specific as well as present.
|
|
|
69
75
|
Mutating the Rust in `ext/c2pa_native/` counts double — it proves the test
|
|
70
76
|
reaches through the FFI boundary rather than stopping at Ruby.
|
|
71
77
|
|
|
72
|
-
###
|
|
78
|
+
### Three traps, all hit while building this suite
|
|
73
79
|
|
|
74
80
|
**A mutation that does not do what you think.** A NUL-handling test appeared to
|
|
75
81
|
survive a mutation that stripped NUL bytes, which would have meant the test was
|
|
@@ -83,6 +89,17 @@ verdict rather than on our own code, mutate in both directions. A harness made
|
|
|
83
89
|
to report no failures and a harness made to report spurious ones fail different
|
|
84
90
|
tests; checking only one leaves the other half unverified.
|
|
85
91
|
|
|
92
|
+
**A probe that measures the wrong thing.** The first two designs for the
|
|
93
|
+
GVL tests looked at wall-clock effects: how long a Ruby thread stalled during
|
|
94
|
+
a native call, and how much progress it made. Both reported the lock released
|
|
95
|
+
when it was held, because `C2PA.sign`'s own `File.exist?` checks release the
|
|
96
|
+
lock (stat does) and the wait to get it back swamped everything else. A
|
|
97
|
+
`sample` of the process showed the main thread spending three quarters of its
|
|
98
|
+
time waiting on the lock after a stat, not inside the native call. The test
|
|
99
|
+
that works measures CPU parallelism, which waiting cannot inflate. When a
|
|
100
|
+
timing test passes under the mutation it was written to catch, suspect the
|
|
101
|
+
probe before the code.
|
|
102
|
+
|
|
86
103
|
## Where there is no oracle
|
|
87
104
|
|
|
88
105
|
Most assertions can be checked against c2pa-rs, by signing a file and reading
|
data/README.md
CHANGED
|
@@ -64,22 +64,44 @@ The native Rust library is compiled automatically during installation. This take
|
|
|
64
64
|
|
|
65
65
|
## Preparing your certificate and key
|
|
66
66
|
|
|
67
|
-
C2PA signing requires an X.509 certificate chain and private key in PEM format.
|
|
67
|
+
C2PA signing requires an X.509 certificate chain and private key in PEM format.
|
|
68
|
+
The certificate file contains the end-entity certificate first, then any
|
|
69
|
+
intermediates, and must **not** include the root.
|
|
70
|
+
|
|
71
|
+
c2pa-rs enforces a certificate profile. An end-entity certificate is rejected
|
|
72
|
+
unless it carries all of:
|
|
73
|
+
|
|
74
|
+
- Basic Constraints `CA:FALSE`, critical
|
|
75
|
+
- Key Usage with `digitalSignature` or `nonRepudiation`, critical
|
|
76
|
+
- an Extended Key Usage that is present and not `any`, critical
|
|
77
|
+
- a Subject Key Identifier
|
|
78
|
+
- an **Authority Key Identifier**
|
|
79
|
+
|
|
80
|
+
The last one is easy to miss — `openssl x509 -req` omits it by default, and the
|
|
81
|
+
certificate is then rejected with nothing more specific than
|
|
82
|
+
`the certificate is invalid`.
|
|
83
|
+
|
|
84
|
+
### Whose CA?
|
|
85
|
+
|
|
86
|
+
A certificate from a CA in the C2PA trust list validates as `Trusted` with no
|
|
87
|
+
configuration.
|
|
88
|
+
|
|
89
|
+
A certificate from your own CA validates as `Valid` and carries
|
|
90
|
+
`signingCredential.untrusted` — which does **not** make the manifest invalid.
|
|
91
|
+
To have it treated as trusted, add your root as a trust anchor; see
|
|
92
|
+
[Configuring trust](#configuring-trust).
|
|
68
93
|
|
|
69
94
|
### Development and testing
|
|
70
95
|
|
|
71
|
-
The
|
|
96
|
+
The test suite generates its own certificates for every supported algorithm:
|
|
72
97
|
|
|
73
98
|
```bash
|
|
74
|
-
|
|
75
|
-
curl -sL -o test_key.pem https://raw.githubusercontent.com/contentauth/c2pa-rs/main/sdk/tests/fixtures/certs/es256.pem
|
|
99
|
+
bundle exec rake fixtures:certs
|
|
76
100
|
```
|
|
77
101
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
Obtain a certificate from a CA trusted by the C2PA ecosystem. The certificate file must contain the full chain (end-entity certificate first, then any intermediates), but must **not** include the root CA.
|
|
102
|
+
They land in `test/fixtures/certs/` and are not committed. See
|
|
103
|
+
[`test/fixtures/generate_certs.rb`](test/fixtures/generate_certs.rb) for a
|
|
104
|
+
worked example of building a chain c2pa-rs accepts.
|
|
83
105
|
|
|
84
106
|
The supported signing algorithms are: `es256`, `es384`, `es512`, `ps256`, `ps384`, `ps512`, `ed25519`.
|
|
85
107
|
|
|
@@ -212,16 +234,42 @@ manifest.add_assertion(
|
|
|
212
234
|
|
|
213
235
|
### Adding ingredients
|
|
214
236
|
|
|
215
|
-
Ingredients record the source assets a file was derived from
|
|
237
|
+
Ingredients record the source assets a file was derived from. Supply the file
|
|
238
|
+
so c2pa-rs can read it:
|
|
216
239
|
|
|
217
240
|
```ruby
|
|
218
241
|
manifest.add_ingredient(
|
|
219
242
|
title: "Original photo",
|
|
220
243
|
format: "image/jpeg",
|
|
221
|
-
instance_id: "xmp:iid:original-uuid-here"
|
|
244
|
+
instance_id: "xmp:iid:original-uuid-here",
|
|
245
|
+
relationship: "componentOf",
|
|
246
|
+
file: "original.jpg"
|
|
222
247
|
)
|
|
223
248
|
```
|
|
224
249
|
|
|
250
|
+
If the ingredient already carries content credentials, its manifest is embedded
|
|
251
|
+
in the signed output and the ingredient points at it. A verifier can then
|
|
252
|
+
follow the chain from your asset back through the original. For a file with no
|
|
253
|
+
credentials there is nothing to carry forward.
|
|
254
|
+
|
|
255
|
+
Omitting `file:` records the description alone. Nothing binds it to any bytes,
|
|
256
|
+
so a verifier cannot check the claim. That form is kept for compatibility;
|
|
257
|
+
prefer the file.
|
|
258
|
+
|
|
259
|
+
### Updating an existing asset
|
|
260
|
+
|
|
261
|
+
For a non-editorial change to an asset, such as correcting metadata, use
|
|
262
|
+
`intent: :update`. The source file is the parent, and the change is recorded
|
|
263
|
+
against it without opening a new editing lineage:
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
manifest = C2PA::Manifest.new(title: "Metadata corrected", intent: :update)
|
|
267
|
+
.add_action(C2PA::Actions::EDITED_METADATA)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
c2pa-rs restricts this mode: there is exactly one ingredient, it is the source
|
|
271
|
+
itself, and the hashed content must not change.
|
|
272
|
+
|
|
225
273
|
### Signing a file
|
|
226
274
|
|
|
227
275
|
The `output` path must not already exist — `C2PA.sign` will raise a `C2PA::SigningError` if the file is already there.
|
|
@@ -275,6 +323,46 @@ C2PA.sign(
|
|
|
275
323
|
)
|
|
276
324
|
```
|
|
277
325
|
|
|
326
|
+
### Signing bytes in memory
|
|
327
|
+
|
|
328
|
+
For data that never touches the filesystem, such as an upload held in a
|
|
329
|
+
request body or an image your application generated, `C2PA.sign_buffer` takes
|
|
330
|
+
the bytes and returns the signed bytes. The format must be given, since there
|
|
331
|
+
is no filename to infer it from.
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
signed = C2PA.sign_buffer(
|
|
335
|
+
data: request.body.read,
|
|
336
|
+
format: "image/jpeg",
|
|
337
|
+
certificate: "cert.pem",
|
|
338
|
+
key: "key.pem",
|
|
339
|
+
manifest: manifest
|
|
340
|
+
)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
The input must be a binary string (`Encoding::BINARY`, which is what
|
|
344
|
+
`File.binread` and `IO#read` on a binary-mode stream return). A string tagged
|
|
345
|
+
UTF-8 is rejected with an `ArgumentError` rather than transcoded, because a
|
|
346
|
+
transcoded JPEG is a corrupt JPEG and nothing notices until a verifier rejects
|
|
347
|
+
it. If you have such a string and know the bytes are intact, call `.b` on it.
|
|
348
|
+
|
|
349
|
+
The same verify-after-sign guard applies. A result that does not validate is
|
|
350
|
+
never returned; `C2PA::SigningError` is raised instead, and `verify: false`
|
|
351
|
+
returns it anyway. `algorithm:` and everything on the manifest, including
|
|
352
|
+
intents and ingredient files, work as they do for `C2PA.sign`.
|
|
353
|
+
|
|
354
|
+
Memory is the trade-off. The input, the copy c2pa-rs works on, and the signed
|
|
355
|
+
result on both sides of the Ruby boundary are resident at once at the peak,
|
|
356
|
+
so budget about four times the size of the asset per call. For a photo that
|
|
357
|
+
is nothing; for a feature-length video it is a reason to use `C2PA.sign` with
|
|
358
|
+
paths instead.
|
|
359
|
+
|
|
360
|
+
Signing and reading release Ruby's global VM lock while c2pa-rs works, so
|
|
361
|
+
other threads in the process keep running. A threaded server signing a large
|
|
362
|
+
video does not stall its other requests for the duration. One consequence:
|
|
363
|
+
`Thread#kill` and `Timeout` cannot interrupt a native call in progress; they
|
|
364
|
+
take effect when it returns.
|
|
365
|
+
|
|
278
366
|
### Reading a manifest
|
|
279
367
|
|
|
280
368
|
```ruby
|
|
@@ -285,6 +373,15 @@ puts active["title"]
|
|
|
285
373
|
puts active["claim_generator_info"].first["name"] # => "ruby-c2pa"
|
|
286
374
|
```
|
|
287
375
|
|
|
376
|
+
From memory, `C2PA.read_buffer` takes the bytes. c2pa-rs identifies most
|
|
377
|
+
formats from the leading bytes, so the format is optional; it is needed for a
|
|
378
|
+
format with no signature to sniff, such as SVG.
|
|
379
|
+
|
|
380
|
+
```ruby
|
|
381
|
+
result = C2PA.read_buffer(data: signed)
|
|
382
|
+
result = C2PA.read_buffer(data: svg_bytes, format: "image/svg+xml")
|
|
383
|
+
```
|
|
384
|
+
|
|
288
385
|
### Naming your application
|
|
289
386
|
|
|
290
387
|
Signed files credit `ruby-c2pa` by default. To credit your own application
|
|
@@ -307,8 +404,8 @@ The signed manifest then reads:
|
|
|
307
404
|
{
|
|
308
405
|
"name": "Acme Editor",
|
|
309
406
|
"version": "2.0",
|
|
310
|
-
"org.
|
|
311
|
-
"org.
|
|
407
|
+
"org.contentauth.c2pa_rs": "0.90.22",
|
|
408
|
+
"org.rubygems.ruby_c2pa": "0.5.0"
|
|
312
409
|
}
|
|
313
410
|
```
|
|
314
411
|
|
|
@@ -322,12 +419,15 @@ calling application.
|
|
|
322
419
|
### Checking the SDK version
|
|
323
420
|
|
|
324
421
|
```ruby
|
|
325
|
-
puts C2PA.sdk_version # => "0.
|
|
422
|
+
puts C2PA.sdk_version # => "0.90.22" (the c2pa-rs version the gem was built against)
|
|
326
423
|
```
|
|
327
424
|
|
|
328
425
|
### Error handling
|
|
329
426
|
|
|
330
|
-
All errors inherit from `C2PA::Error`, so you can rescue broadly or narrowly
|
|
427
|
+
All errors inherit from `C2PA::Error`, so you can rescue broadly or narrowly.
|
|
428
|
+
`SigningError` covers signing and the post-signing verification, `ReadError`
|
|
429
|
+
reading, `InvalidManifestError` anything the builder rejects, and
|
|
430
|
+
`InvalidSettingsError` anything `C2PA.configure` cannot use.
|
|
331
431
|
|
|
332
432
|
```ruby
|
|
333
433
|
begin
|
|
@@ -344,6 +444,12 @@ rescue C2PA::ReadError => e
|
|
|
344
444
|
puts "Could not read manifest: #{e.message}"
|
|
345
445
|
end
|
|
346
446
|
|
|
447
|
+
begin
|
|
448
|
+
C2PA.configure { |config| config.trust_anchors = "ca/root.pem" }
|
|
449
|
+
rescue C2PA::InvalidSettingsError => e
|
|
450
|
+
puts "Settings not usable: #{e.message}"
|
|
451
|
+
end
|
|
452
|
+
|
|
347
453
|
# Or rescue any C2PA error broadly
|
|
348
454
|
begin
|
|
349
455
|
C2PA.sign(file: "photo.jpg", output: "photo_signed.jpg", certificate: "cert.pem", key: "key.pem", manifest: manifest)
|
|
@@ -352,14 +458,89 @@ rescue C2PA::Error => e
|
|
|
352
458
|
end
|
|
353
459
|
```
|
|
354
460
|
|
|
461
|
+
## Configuring trust
|
|
462
|
+
|
|
463
|
+
c2pa-rs checks the signing certificate against a trust list, and reports
|
|
464
|
+
`Trusted` when it chains to a root that list contains. That happens by default,
|
|
465
|
+
so a certificate from a CA in the C2PA trust list needs no configuration.
|
|
466
|
+
|
|
467
|
+
For a private or enterprise CA, add its root:
|
|
468
|
+
|
|
469
|
+
```ruby
|
|
470
|
+
C2PA.configure do |config|
|
|
471
|
+
config.trust_anchors = "ca/root.pem" # a path, or the PEM text itself
|
|
472
|
+
end
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Files signed by a certificate chaining to it then validate as `Trusted` rather
|
|
476
|
+
than carrying `signingCredential.untrusted`.
|
|
477
|
+
|
|
478
|
+
### Offline and air-gapped environments
|
|
479
|
+
|
|
480
|
+
Reading an asset may fetch a remote manifest over the network, and revocation
|
|
481
|
+
checking may contact an OCSP responder. Both can be turned off:
|
|
482
|
+
|
|
483
|
+
```ruby
|
|
484
|
+
C2PA.configure do |config|
|
|
485
|
+
config.remote_manifest_fetch = false
|
|
486
|
+
config.ocsp_fetch = false
|
|
487
|
+
end
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Thumbnails
|
|
491
|
+
|
|
492
|
+
c2pa-rs can embed a thumbnail of the asset in its manifest, and of each
|
|
493
|
+
ingredient supplied as a file. Verify tools show it alongside the credentials.
|
|
494
|
+
It is off unless you turn it on:
|
|
495
|
+
|
|
496
|
+
```ruby
|
|
497
|
+
C2PA.configure do |config|
|
|
498
|
+
config.thumbnails = true
|
|
499
|
+
config.thumbnail_size = 512 # longest edge in pixels
|
|
500
|
+
end
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
The default is off because c2pa-rs scales to a fixed long edge and upscales to
|
|
504
|
+
reach it. Its own default is 1024, so a 160×120 image gets a 1024×768
|
|
505
|
+
thumbnail, roughly ten times the size of the asset it describes. Set
|
|
506
|
+
`thumbnail_size` no larger than your assets, or leave thumbnails off for small
|
|
507
|
+
images.
|
|
508
|
+
|
|
509
|
+
Thumbnails are produced for JPEG, PNG, WebP and TIFF. Other formats sign
|
|
510
|
+
without one; c2pa-rs treats that as non-fatal.
|
|
511
|
+
|
|
512
|
+
### Everything configurable
|
|
513
|
+
|
|
514
|
+
| Setting | Default | Purpose |
|
|
515
|
+
|---------|---------|---------|
|
|
516
|
+
| `trust_anchors` | none | additional roots to trust, as PEM |
|
|
517
|
+
| `trust_list` | C2PA list | replaces the trust list rather than adding to it |
|
|
518
|
+
| `allowed_certificates` | none | explicitly allowed certificates, as PEM |
|
|
519
|
+
| `verify_trust` | `true` | whether trust is checked at all |
|
|
520
|
+
| `remote_manifest_fetch` | `true` | whether reading may fetch over the network |
|
|
521
|
+
| `ocsp_fetch` | `false` | whether revocation is checked over OCSP |
|
|
522
|
+
| `thumbnails` | `false` | embed a thumbnail of the asset and of file-backed ingredients |
|
|
523
|
+
| `thumbnail_size` | 1024 | longest edge of the thumbnail, in pixels |
|
|
524
|
+
| `thumbnail_format` | smallest | `:jpeg`, `:png` or `:gif` |
|
|
525
|
+
| `thumbnail_quality` | `:medium` | `:low`, `:medium` or `:high` |
|
|
526
|
+
|
|
527
|
+
Settings are global and apply to subsequent calls. Only values you set are
|
|
528
|
+
sent, so anything left alone keeps c2pa-rs's own default, with one exception:
|
|
529
|
+
`thumbnails` is always sent, because this gem's default differs from c2pa-rs's.
|
|
530
|
+
`C2PA.configure` with no block resets everything.
|
|
531
|
+
|
|
532
|
+
Turning `verify_trust` off means nothing is ever reported as untrusted, which
|
|
533
|
+
in a library for establishing provenance is rarely what you want. It exists for
|
|
534
|
+
environments that cannot reach a trust list at all.
|
|
535
|
+
|
|
355
536
|
## Supported file formats
|
|
356
537
|
|
|
357
538
|
Each format below has a fixture and a signing test in the suite: the file is
|
|
358
539
|
signed, read back, and asserted to validate.
|
|
359
540
|
|
|
360
|
-
| Format | MIME type |
|
|
361
|
-
|
|
362
|
-
| JPEG | `image/jpeg` |
|
|
541
|
+
| Format | MIME type | |
|
|
542
|
+
|--------|-----------|--|
|
|
543
|
+
| JPEG | `image/jpeg` | |
|
|
363
544
|
| PNG | `image/png` |
|
|
364
545
|
| WebP | `image/webp` |
|
|
365
546
|
| TIFF | `image/tiff` |
|
|
@@ -369,19 +550,31 @@ signed, read back, and asserted to validate.
|
|
|
369
550
|
| MOV | `video/quicktime` |
|
|
370
551
|
| MP3 | `audio/mpeg` |
|
|
371
552
|
| WAV | `audio/wav` |
|
|
553
|
+
| PDF | `application/pdf` | read only, see below |
|
|
372
554
|
|
|
373
555
|
The format is detected automatically from the file extension.
|
|
374
556
|
|
|
375
557
|
JPEG XL must be in the ISOBMFF container form. A bare codestream has no boxes
|
|
376
558
|
to hold a manifest, and c2pa-rs rejects it.
|
|
377
559
|
|
|
378
|
-
###
|
|
560
|
+
### PDF is read-only
|
|
561
|
+
|
|
562
|
+
`C2PA.read` works on a PDF that carries content credentials, such as one signed
|
|
563
|
+
by Adobe Acrobat. `C2PA.sign` does not: c2pa-rs has no PDF writer at any
|
|
564
|
+
version. `get_writer` returns `None` and `save_cai_store` returns
|
|
565
|
+
`NotImplemented`, and upstream closed the request to expose one in December
|
|
566
|
+
2025 (contentauth/c2pa-rs#527). Signing a PDF raises `C2PA::SigningError`
|
|
567
|
+
with `type is unsupported`.
|
|
379
568
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
569
|
+
Earlier releases of this gem listed PDF as signable. That was never correct.
|
|
570
|
+
|
|
571
|
+
One limit on what the test suite can show. It proves the PDF handler is active,
|
|
572
|
+
by reading a PDF with no credentials and getting `no JUMBF data found` rather
|
|
573
|
+
than `type is unsupported`. It cannot prove that a signed PDF returns its
|
|
574
|
+
manifest, because nothing available can produce one: c2pa-rs cannot write
|
|
575
|
+
them, and no local tool can either. The code doing the reading is c2pa-rs's
|
|
576
|
+
own and is tested upstream; what is untested here is only this gem's
|
|
577
|
+
integration with the success path.
|
|
385
578
|
|
|
386
579
|
### Test fixtures
|
|
387
580
|
|
|
@@ -417,20 +610,35 @@ Ruby (C2PA.sign)
|
|
|
417
610
|
▼
|
|
418
611
|
Rust (C2PA::Native.sign_file)
|
|
419
612
|
│
|
|
420
|
-
│
|
|
613
|
+
│ c2pa-rs Builder, through a shared Context
|
|
421
614
|
▼
|
|
422
615
|
c2pa-rs — embeds signed manifest into the file
|
|
423
616
|
```
|
|
424
617
|
|
|
425
|
-
The Rust extension (`ext/c2pa_native/src/lib.rs`) defines `C2PA::Native` with
|
|
618
|
+
The Rust extension (`ext/c2pa_native/src/lib.rs`) defines `C2PA::Native` with six methods:
|
|
426
619
|
|
|
427
620
|
| Method | Description |
|
|
428
621
|
|--------|-------------|
|
|
429
|
-
| `C2PA::Native.sign_file` | Sign a file and write the result |
|
|
622
|
+
| `C2PA::Native.sign_file` | Sign a file and write the result. Takes the manifest JSON, an optional intent, and any ingredient files |
|
|
623
|
+
| `C2PA::Native.sign_buffer` | The same over bytes: a binary string in, the signed binary string out |
|
|
430
624
|
| `C2PA::Native.read_file` | Read and return the manifest JSON |
|
|
625
|
+
| `C2PA::Native.read_buffer` | The same over bytes, with an optional format hint |
|
|
626
|
+
| `C2PA::Native.configure` | Replace the shared c2pa-rs Context with one built from a settings document |
|
|
431
627
|
| `C2PA::Native.sdk_version` | Return the c2pa-rs version string |
|
|
432
628
|
|
|
433
|
-
|
|
629
|
+
All four run with the global VM lock released, so other Ruby threads are not
|
|
630
|
+
blocked while c2pa-rs hashes and signs. The bytes and paths are copied out of
|
|
631
|
+
Ruby before the lock goes, and the result is turned into a Ruby object after
|
|
632
|
+
it is back; nothing in between touches the interpreter.
|
|
633
|
+
|
|
634
|
+
Signing and reading go through one c2pa-rs `Context`, built once and shared
|
|
635
|
+
across threads. `C2PA.configure` replaces it rather than mutating it, so a
|
|
636
|
+
signing call already in flight keeps the settings it started with.
|
|
637
|
+
|
|
638
|
+
Input validation (missing files, invalid manifests, unreadable ingredient
|
|
639
|
+
files, unusable settings) is handled in Ruby before calling into Rust. Errors
|
|
640
|
+
from the native layer are caught and re-raised as typed `C2PA::Error`
|
|
641
|
+
subclasses.
|
|
434
642
|
|
|
435
643
|
## Contributing
|
|
436
644
|
|