ruby-c2pa 0.3.0 → 0.4.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 +49 -1
- data/README.md +128 -20
- data/ext/c2pa_native/Cargo.lock +754 -667
- data/ext/c2pa_native/Cargo.toml +1 -1
- data/ext/c2pa_native/src/lib.rs +102 -5
- data/lib/c2pa/config.rb +90 -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 +38 -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: 66e048e5359881f08def1d3fd82ed11fb8b9b3dfc0d8232d9dbc145026841d39
|
|
4
|
+
data.tar.gz: bd9108538b9e6d47bd3684d974d2145963345cbdbe67ec44250a1892dd277957
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65b8729aa03c0fb5145f1631fc5c847960be95cfd792553d0352c9c9e7f54451a077b3c7eb599c45ff3b7007698aaa9a91d3f05848c0307f192c5d5de2333c25
|
|
7
|
+
data.tar.gz: 0f46873209bbc9b081a2ef24c276adb0daf079466366a01a712a504d3f0df1eb64b20949fa2f171c469eba9e0da48f429c569b09cba8d5ee48f6e978d3548f72
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] — 2026-09-11
|
|
11
|
+
|
|
12
|
+
Adds capability on top of 0.3.0. Nothing is removed and no existing call
|
|
13
|
+
changes behaviour, so this is a minor release.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Provenance chaining through ingredients. `add_ingredient` takes an optional
|
|
18
|
+
`file:`. When the file carries content credentials, its manifest is embedded
|
|
19
|
+
in the signed output and the ingredient points at it, so a verifier can follow
|
|
20
|
+
the chain from your asset back through the original. The description-only
|
|
21
|
+
form is unchanged.
|
|
22
|
+
- `intent: :update` on `C2PA::Manifest`, for non-editorial changes such as
|
|
23
|
+
correcting metadata. The source file is the parent.
|
|
24
|
+
- `C2PA.configure`, for trust and verification settings. Add a private CA's
|
|
25
|
+
root as a trust anchor and its certificates validate as `Trusted`; disable
|
|
26
|
+
`remote_manifest_fetch` and `ocsp_fetch` for environments without network
|
|
27
|
+
access. Only values you set are sent, so defaults are c2pa-rs's own.
|
|
28
|
+
- Reading content credentials from PDFs. `C2PA.read` now parses a PDF and
|
|
29
|
+
returns its manifest if one is present. Signing a PDF remains impossible;
|
|
30
|
+
c2pa-rs has no PDF writer and upstream closed the request to add one.
|
|
31
|
+
- `C2PA::InvalidSettingsError`, raised by `C2PA.configure` for unusable input.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- c2pa-rs 0.90.15 → 0.90.22. The range includes security fixes: `h2` updated
|
|
36
|
+
for RUSTSEC-2026-0258, `chacha20` moved off a yanked version, hardening of
|
|
37
|
+
BMFF chunk-index handling and CAWG identity bindings. The lockfile ships in
|
|
38
|
+
the gem, so installers get these once they upgrade.
|
|
39
|
+
- The native layer uses c2pa-rs's Context API rather than the deprecated
|
|
40
|
+
`Builder::from_json` and `Reader::from_file`, which read settings from
|
|
41
|
+
thread-local state. One shared Context is reused across calls. Signing from
|
|
42
|
+
several threads concurrently is now tested.
|
|
43
|
+
- CI fails if a deprecated c2pa-rs API reappears.
|
|
44
|
+
|
|
45
|
+
### Documentation
|
|
46
|
+
|
|
47
|
+
- The certificate section no longer claims certificates must chain to a CA in
|
|
48
|
+
the C2PA trust list, or that self-signed certificates are rejected as such.
|
|
49
|
+
A private CA works; its certificates carry `signingCredential.untrusted`
|
|
50
|
+
until the root is added as an anchor. The certificate profile c2pa-rs
|
|
51
|
+
enforces is listed, including the Authority Key Identifier that
|
|
52
|
+
`openssl x509 -req` omits by default.
|
|
53
|
+
- PDF is listed as read-only, with a note on what the test suite can and
|
|
54
|
+
cannot show: nothing available can produce a C2PA-signed PDF, so reading one
|
|
55
|
+
is untested here, though the code doing it is c2pa-rs's own.
|
|
56
|
+
|
|
10
57
|
## [0.3.0] — 2026-08-25
|
|
11
58
|
|
|
12
59
|
Runs on c2pa-rs 0.90, and fixes every defect found while building a test suite
|
|
@@ -99,7 +146,8 @@ Tagged retroactively. See the v0.2.1 tag for the defects it shipped with.
|
|
|
99
146
|
|
|
100
147
|
Tagged retroactively.
|
|
101
148
|
|
|
102
|
-
[Unreleased]: https://github.com/eddorre/ruby-c2pa/compare/v0.
|
|
149
|
+
[Unreleased]: https://github.com/eddorre/ruby-c2pa/compare/v0.4.0...HEAD
|
|
150
|
+
[0.4.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.3.0...v0.4.0
|
|
103
151
|
[0.3.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.2.1...v0.3.0
|
|
104
152
|
[0.2.1]: https://github.com/eddorre/ruby-c2pa/compare/v0.2.0...v0.2.1
|
|
105
153
|
[0.2.0]: https://github.com/eddorre/ruby-c2pa/releases/tag/v0.2.0
|
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.
|
|
@@ -352,14 +400,62 @@ rescue C2PA::Error => e
|
|
|
352
400
|
end
|
|
353
401
|
```
|
|
354
402
|
|
|
403
|
+
## Configuring trust
|
|
404
|
+
|
|
405
|
+
c2pa-rs checks the signing certificate against a trust list, and reports
|
|
406
|
+
`Trusted` when it chains to a root that list contains. That happens by default,
|
|
407
|
+
so a certificate from a CA in the C2PA trust list needs no configuration.
|
|
408
|
+
|
|
409
|
+
For a private or enterprise CA, add its root:
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
C2PA.configure do |config|
|
|
413
|
+
config.trust_anchors = "ca/root.pem" # a path, or the PEM text itself
|
|
414
|
+
end
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Files signed by a certificate chaining to it then validate as `Trusted` rather
|
|
418
|
+
than carrying `signingCredential.untrusted`.
|
|
419
|
+
|
|
420
|
+
### Offline and air-gapped environments
|
|
421
|
+
|
|
422
|
+
Reading an asset may fetch a remote manifest over the network, and revocation
|
|
423
|
+
checking may contact an OCSP responder. Both can be turned off:
|
|
424
|
+
|
|
425
|
+
```ruby
|
|
426
|
+
C2PA.configure do |config|
|
|
427
|
+
config.remote_manifest_fetch = false
|
|
428
|
+
config.ocsp_fetch = false
|
|
429
|
+
end
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Everything configurable
|
|
433
|
+
|
|
434
|
+
| Setting | Default | Purpose |
|
|
435
|
+
|---------|---------|---------|
|
|
436
|
+
| `trust_anchors` | none | additional roots to trust, as PEM |
|
|
437
|
+
| `trust_list` | C2PA list | replaces the trust list rather than adding to it |
|
|
438
|
+
| `allowed_certificates` | none | explicitly allowed certificates, as PEM |
|
|
439
|
+
| `verify_trust` | `true` | whether trust is checked at all |
|
|
440
|
+
| `remote_manifest_fetch` | `true` | whether reading may fetch over the network |
|
|
441
|
+
| `ocsp_fetch` | `false` | whether revocation is checked over OCSP |
|
|
442
|
+
|
|
443
|
+
Settings are global and apply to subsequent calls. Only values you set are
|
|
444
|
+
sent, so anything left alone keeps c2pa-rs's own default. `C2PA.configure` with
|
|
445
|
+
no block resets everything.
|
|
446
|
+
|
|
447
|
+
Turning `verify_trust` off means nothing is ever reported as untrusted, which
|
|
448
|
+
in a library for establishing provenance is rarely what you want. It exists for
|
|
449
|
+
environments that cannot reach a trust list at all.
|
|
450
|
+
|
|
355
451
|
## Supported file formats
|
|
356
452
|
|
|
357
453
|
Each format below has a fixture and a signing test in the suite: the file is
|
|
358
454
|
signed, read back, and asserted to validate.
|
|
359
455
|
|
|
360
|
-
| Format | MIME type |
|
|
361
|
-
|
|
362
|
-
| JPEG | `image/jpeg` |
|
|
456
|
+
| Format | MIME type | |
|
|
457
|
+
|--------|-----------|--|
|
|
458
|
+
| JPEG | `image/jpeg` | |
|
|
363
459
|
| PNG | `image/png` |
|
|
364
460
|
| WebP | `image/webp` |
|
|
365
461
|
| TIFF | `image/tiff` |
|
|
@@ -369,19 +465,31 @@ signed, read back, and asserted to validate.
|
|
|
369
465
|
| MOV | `video/quicktime` |
|
|
370
466
|
| MP3 | `audio/mpeg` |
|
|
371
467
|
| WAV | `audio/wav` |
|
|
468
|
+
| PDF | `application/pdf` | read only, see below |
|
|
372
469
|
|
|
373
470
|
The format is detected automatically from the file extension.
|
|
374
471
|
|
|
375
472
|
JPEG XL must be in the ISOBMFF container form. A bare codestream has no boxes
|
|
376
473
|
to hold a manifest, and c2pa-rs rejects it.
|
|
377
474
|
|
|
378
|
-
###
|
|
475
|
+
### PDF is read-only
|
|
476
|
+
|
|
477
|
+
`C2PA.read` works on a PDF that carries content credentials, such as one signed
|
|
478
|
+
by Adobe Acrobat. `C2PA.sign` does not: c2pa-rs has no PDF writer at any
|
|
479
|
+
version. `get_writer` returns `None` and `save_cai_store` returns
|
|
480
|
+
`NotImplemented`, and upstream closed the request to expose one in December
|
|
481
|
+
2025 (contentauth/c2pa-rs#527). Signing a PDF raises `C2PA::SigningError`
|
|
482
|
+
with `type is unsupported`.
|
|
483
|
+
|
|
484
|
+
Earlier releases of this gem listed PDF as signable. That was never correct.
|
|
379
485
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
`
|
|
383
|
-
|
|
384
|
-
|
|
486
|
+
One limit on what the test suite can show. It proves the PDF handler is active,
|
|
487
|
+
by reading a PDF with no credentials and getting `no JUMBF data found` rather
|
|
488
|
+
than `type is unsupported`. It cannot prove that a signed PDF returns its
|
|
489
|
+
manifest, because nothing available can produce one: c2pa-rs cannot write
|
|
490
|
+
them, and no local tool can either. The code doing the reading is c2pa-rs's
|
|
491
|
+
own and is tested upstream; what is untested here is only this gem's
|
|
492
|
+
integration with the success path.
|
|
385
493
|
|
|
386
494
|
### Test fixtures
|
|
387
495
|
|