ruby-c2pa 0.4.0 → 0.6.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 +93 -1
- data/CONTRIBUTING.md +30 -4
- data/README.md +244 -30
- data/ext/c2pa_native/Cargo.lock +202 -290
- data/ext/c2pa_native/Cargo.toml +9 -1
- data/ext/c2pa_native/src/lib.rs +271 -40
- data/lib/c2pa/config.rb +65 -3
- data/lib/c2pa/manifest.rb +42 -6
- data/lib/c2pa/version.rb +1 -1
- data/lib/c2pa.rb +153 -10
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd571866dd99e9811e93072f9e642625f844a418bc7878d437905231b5cda679
|
|
4
|
+
data.tar.gz: 8854aaaf99e78cce57e4ba8f60102cf64e9aeda243c17b1395878dedb83eb904
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3839eadfa75e5d7f3bd2eb22164cec8301195a4fdb7eee7d39cccafdf8abc67fa09b855ece3a43a0bcc5a6e3dddcb408bc1c4f5c73fa0ba5097fd51623384e1
|
|
7
|
+
data.tar.gz: d84bd7979e4a3b14f7039ca3e0f5c3b3cf4b35f663d333cd8d246d52afcbc910602d7a462e5bf5dd22d32b5be3fa603a29f224bc84a848cfd13da46b81bb66cc
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,96 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] — 2026-09-29
|
|
11
|
+
|
|
12
|
+
Adds capability on top of 0.5.0. Nothing is removed and no existing call
|
|
13
|
+
changes behaviour, so this is a minor release. Building it needs Rust 1.96
|
|
14
|
+
or newer, which c2pa-rs 0.91 requires.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Detached manifests. `C2PA.sign(sidecar:)` writes the manifest to its own
|
|
19
|
+
`.c2pa` file instead of embedding it, and leaves the asset as it was hashed.
|
|
20
|
+
`C2PA.read(manifest_file:)` and `C2PA.read_buffer(manifest_data:)` validate an
|
|
21
|
+
asset against a sidecar. A sidecar with the asset's name and a `.c2pa`
|
|
22
|
+
extension is found without being named. The verify guard checks the pair and
|
|
23
|
+
removes both files if it fails.
|
|
24
|
+
- Manifests stored under the `c2md` JUMBF type are read. The specification
|
|
25
|
+
requires readers to accept them; c2pa-rs 0.91 does, and earlier versions
|
|
26
|
+
skipped them.
|
|
27
|
+
- Signing and reading EPUB, Word (DOCX), OpenDocument text (ODT) and OpenXPS
|
|
28
|
+
files. The manifest goes in `META-INF/content_credential.c2pa`, and every
|
|
29
|
+
other part of the package is covered by the signature. c2pa-rs could only
|
|
30
|
+
read uncompressed ZIP entries, which no real document uses, so the extension
|
|
31
|
+
now enables deflate in its zip dependency. `C2PA.read_buffer` needs
|
|
32
|
+
`format:` for these, since they all start with the same ZIP header.
|
|
33
|
+
- `C2PA::Config#exclude_free_and_skip_boxes`. Set it to false when signing
|
|
34
|
+
MP4, MOV and other BMFF files so that any later change to their `free` and
|
|
35
|
+
`skip` padding boxes breaks the signature. Left unset, c2pa-rs excludes
|
|
36
|
+
them, as it always has.
|
|
37
|
+
- `C2PA::Config#allow_redirects`. Set it to false to stop network fetches
|
|
38
|
+
during reading and validation from following redirects. Left unset, c2pa-rs
|
|
39
|
+
follows redirects but refuses those that point at internal addresses.
|
|
40
|
+
- Signing PDFs. c2pa-rs added a PDF writer in 0.91.1, after years of having
|
|
41
|
+
none, so `C2PA.sign` and `C2PA.sign_buffer` now accept PDFs and the manifest
|
|
42
|
+
goes into the document's associated files. Every release up to 0.5.0
|
|
43
|
+
documented PDF signing as impossible, which it was at the time.
|
|
44
|
+
- `Manifest#add_ingredient` takes `digital_source_type:`, recording how an
|
|
45
|
+
ingredient with no content credentials was produced. Giving one for a file
|
|
46
|
+
that carries credentials raises `C2PA::InvalidManifestError`.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- `Manifest#add_action` raises `C2PA::InvalidManifestError` when
|
|
51
|
+
`parameters` includes `relatedAssertions`. c2pa-rs 0.91 checks these links
|
|
52
|
+
when reading but cannot create them, so the gem has no genuine value to
|
|
53
|
+
send.
|
|
54
|
+
- Built against c2pa-rs 0.91.1, which needs Rust 1.96 or newer to compile.
|
|
55
|
+
0.91.1 also carries a batch of hardening fixes over 0.91.0: a panic on
|
|
56
|
+
oversized XMP when writing JPEG APP1, an underflow in XMP trailer parsing,
|
|
57
|
+
unbounded TIFF IFD entry counts, uncapped ID3v2 frame decompression, and
|
|
58
|
+
corrections to OCSP responder validation. That release validates manifests
|
|
59
|
+
before writing by default; the gem turns that off and keeps its own
|
|
60
|
+
verify-after-sign guard, which reads the finished output back and checks
|
|
61
|
+
its hashes. Errors and the `verify:` option behave as before.
|
|
62
|
+
|
|
63
|
+
## [0.5.0] — 2026-09-12
|
|
64
|
+
|
|
65
|
+
Adds capability on top of 0.4.0. Nothing is removed and no existing call
|
|
66
|
+
changes behaviour, so this is a minor release. The one change every caller
|
|
67
|
+
will feel is that native calls no longer hold the GVL, which only makes other
|
|
68
|
+
threads faster.
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
|
|
72
|
+
- Buffer signing. `C2PA.sign_buffer(data:, format:, ...)` signs bytes held in
|
|
73
|
+
memory and returns the signed bytes; `C2PA.read_buffer(data:, format: nil)`
|
|
74
|
+
reads them back. Both require a binary string and raise `ArgumentError` for
|
|
75
|
+
any other encoding rather than transcoding the asset. The verify-after-sign
|
|
76
|
+
guard applies to buffers as it does to files. Memory use is about four
|
|
77
|
+
times the asset.
|
|
78
|
+
- The native calls (`sign_file`, `sign_buffer`, `read_file`, `read_buffer`)
|
|
79
|
+
release Ruby's global VM lock while c2pa-rs runs. Other Ruby threads keep
|
|
80
|
+
running during a sign; previously they were blocked until it returned.
|
|
81
|
+
Measured alongside a busy Ruby thread, the process now uses about 1.9
|
|
82
|
+
CPU-seconds per wall-second against 1.0 before. `Thread#kill` and
|
|
83
|
+
`Timeout` take effect when the native call returns, not during it.
|
|
84
|
+
- Thumbnails. `C2PA.configure` gains `thumbnails`, `thumbnail_size`,
|
|
85
|
+
`thumbnail_format` and `thumbnail_quality`. When enabled, a thumbnail of the
|
|
86
|
+
asset is embedded in its manifest, and of each ingredient supplied as a file.
|
|
87
|
+
Off by default: c2pa-rs upscales to its long-edge setting, so at its default
|
|
88
|
+
of 1024 a 160×120 image gets a 1024×768 thumbnail ten times its own size.
|
|
89
|
+
Produced for JPEG, PNG, WebP and TIFF; other formats sign without one.
|
|
90
|
+
|
|
91
|
+
### Changed
|
|
92
|
+
|
|
93
|
+
- rb-sys is now a direct dependency of the native extension, for
|
|
94
|
+
`rb_thread_call_without_gvl`, which magnus does not wrap. It resolves to the
|
|
95
|
+
same copy magnus already uses.
|
|
96
|
+
- The native extension is built with c2pa-rs's `add_thumbnails` feature, which
|
|
97
|
+
adds the `image` crate: 16 more crates, about 17 seconds on a cold compile,
|
|
98
|
+
and 1.5 MB on the compiled extension.
|
|
99
|
+
|
|
10
100
|
## [0.4.0] — 2026-09-11
|
|
11
101
|
|
|
12
102
|
Adds capability on top of 0.3.0. Nothing is removed and no existing call
|
|
@@ -146,7 +236,9 @@ Tagged retroactively. See the v0.2.1 tag for the defects it shipped with.
|
|
|
146
236
|
|
|
147
237
|
Tagged retroactively.
|
|
148
238
|
|
|
149
|
-
[Unreleased]: https://github.com/eddorre/ruby-c2pa/compare/v0.
|
|
239
|
+
[Unreleased]: https://github.com/eddorre/ruby-c2pa/compare/v0.6.0...HEAD
|
|
240
|
+
[0.6.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.5.0...v0.6.0
|
|
241
|
+
[0.5.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.4.0...v0.5.0
|
|
150
242
|
[0.4.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.3.0...v0.4.0
|
|
151
243
|
[0.3.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.2.1...v0.3.0
|
|
152
244
|
[0.2.1]: https://github.com/eddorre/ruby-c2pa/compare/v0.2.0...v0.2.1
|
data/CONTRIBUTING.md
CHANGED
|
@@ -14,7 +14,7 @@ Regenerating the media fixtures is the one thing that needs extra tooling, and
|
|
|
14
14
|
only if you are changing them:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
brew install ffmpeg webp jpeg-xl exiftool # macOS
|
|
17
|
+
brew install ffmpeg webp jpeg-xl exiftool # macOS; zip ships with it
|
|
18
18
|
./test/fixtures/generate.sh
|
|
19
19
|
```
|
|
20
20
|
|
|
@@ -29,7 +29,7 @@ that suite passed while the gem:
|
|
|
29
29
|
|
|
30
30
|
- aborted the Ruby process on any TIFF input
|
|
31
31
|
- emitted manifests that modern verifiers reject
|
|
32
|
-
- advertised PDF signing, which c2pa-rs
|
|
32
|
+
- advertised PDF signing, which no c2pa-rs release could do until 0.91.1
|
|
33
33
|
- documented an editing workflow that has never produced a valid file
|
|
34
34
|
|
|
35
35
|
The suite passed because its assertions compared the code to itself:
|
|
@@ -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
|
+
### Four 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,26 @@ 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
|
+
|
|
103
|
+
**An assertion satisfied by the wrong thing.** A test for "signing a PDF
|
|
104
|
+
leaves it a readable PDF" checked that the output still contained
|
|
105
|
+
`ruby-c2pa`, the text on the fixture's page. It passed under a mutation that
|
|
106
|
+
rewrote the page content before signing, because `ruby-c2pa` also appears in
|
|
107
|
+
the manifest's claim generator: the assertion was reading the manifest, not
|
|
108
|
+
the document. Matching the page's text operator, `(ruby-c2pa) Tj`, fixed it.
|
|
109
|
+
When a string you assert on could come from more than one place in the
|
|
110
|
+
output, match something only the thing under test can produce.
|
|
111
|
+
|
|
86
112
|
## Where there is no oracle
|
|
87
113
|
|
|
88
114
|
Most assertions can be checked against c2pa-rs, by signing a file and reading
|
data/README.md
CHANGED
|
@@ -27,7 +27,9 @@ The binding layer is a native Ruby extension written in Rust using [magnus](http
|
|
|
27
27
|
## Requirements
|
|
28
28
|
|
|
29
29
|
- Ruby >= 3.0
|
|
30
|
-
- Rust and Cargo (to compile the native library)
|
|
30
|
+
- Rust >= 1.96 and Cargo (to compile the native library). c2pa-rs 0.91 sets
|
|
31
|
+
that floor; an older toolchain stops with
|
|
32
|
+
`c2pa@0.91.1 requires rustc 1.96.0` during installation.
|
|
31
33
|
- OpenSSL (usually already present on macOS and Linux)
|
|
32
34
|
|
|
33
35
|
### Installing Rust
|
|
@@ -256,6 +258,23 @@ Omitting `file:` records the description alone. Nothing binds it to any bytes,
|
|
|
256
258
|
so a verifier cannot check the claim. That form is kept for compatibility;
|
|
257
259
|
prefer the file.
|
|
258
260
|
|
|
261
|
+
An ingredient with no credentials can say how it was produced, the same way a
|
|
262
|
+
`c2pa.created` action does for the asset itself:
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
manifest.add_ingredient(
|
|
266
|
+
title: "Generated background",
|
|
267
|
+
format: "image/png",
|
|
268
|
+
instance_id: "xmp:iid:background-uuid-here",
|
|
269
|
+
relationship: "componentOf",
|
|
270
|
+
file: "background.png",
|
|
271
|
+
digital_source_type: C2PA::DigitalSourceTypes::TRAINED_ALGORITHMIC_MEDIA
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
An ingredient that carries content credentials already records its own origin,
|
|
276
|
+
so `add_ingredient` raises `C2PA::InvalidManifestError` if you give it one.
|
|
277
|
+
|
|
259
278
|
### Updating an existing asset
|
|
260
279
|
|
|
261
280
|
For a non-editorial change to an asset, such as correcting metadata, use
|
|
@@ -323,6 +342,46 @@ C2PA.sign(
|
|
|
323
342
|
)
|
|
324
343
|
```
|
|
325
344
|
|
|
345
|
+
### Signing bytes in memory
|
|
346
|
+
|
|
347
|
+
For data that never touches the filesystem, such as an upload held in a
|
|
348
|
+
request body or an image your application generated, `C2PA.sign_buffer` takes
|
|
349
|
+
the bytes and returns the signed bytes. The format must be given, since there
|
|
350
|
+
is no filename to infer it from.
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
signed = C2PA.sign_buffer(
|
|
354
|
+
data: request.body.read,
|
|
355
|
+
format: "image/jpeg",
|
|
356
|
+
certificate: "cert.pem",
|
|
357
|
+
key: "key.pem",
|
|
358
|
+
manifest: manifest
|
|
359
|
+
)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
The input must be a binary string (`Encoding::BINARY`, which is what
|
|
363
|
+
`File.binread` and `IO#read` on a binary-mode stream return). A string tagged
|
|
364
|
+
UTF-8 is rejected with an `ArgumentError` rather than transcoded, because a
|
|
365
|
+
transcoded JPEG is a corrupt JPEG and nothing notices until a verifier rejects
|
|
366
|
+
it. If you have such a string and know the bytes are intact, call `.b` on it.
|
|
367
|
+
|
|
368
|
+
The same verify-after-sign guard applies. A result that does not validate is
|
|
369
|
+
never returned; `C2PA::SigningError` is raised instead, and `verify: false`
|
|
370
|
+
returns it anyway. `algorithm:` and everything on the manifest, including
|
|
371
|
+
intents and ingredient files, work as they do for `C2PA.sign`.
|
|
372
|
+
|
|
373
|
+
Memory is the trade-off. The input, the copy c2pa-rs works on, and the signed
|
|
374
|
+
result on both sides of the Ruby boundary are resident at once at the peak,
|
|
375
|
+
so budget about four times the size of the asset per call. For a photo that
|
|
376
|
+
is nothing; for a feature-length video it is a reason to use `C2PA.sign` with
|
|
377
|
+
paths instead.
|
|
378
|
+
|
|
379
|
+
Signing and reading release Ruby's global VM lock while c2pa-rs works, so
|
|
380
|
+
other threads in the process keep running. A threaded server signing a large
|
|
381
|
+
video does not stall its other requests for the duration. One consequence:
|
|
382
|
+
`Thread#kill` and `Timeout` cannot interrupt a native call in progress; they
|
|
383
|
+
take effect when it returns.
|
|
384
|
+
|
|
326
385
|
### Reading a manifest
|
|
327
386
|
|
|
328
387
|
```ruby
|
|
@@ -333,6 +392,49 @@ puts active["title"]
|
|
|
333
392
|
puts active["claim_generator_info"].first["name"] # => "ruby-c2pa"
|
|
334
393
|
```
|
|
335
394
|
|
|
395
|
+
From memory, `C2PA.read_buffer` takes the bytes. c2pa-rs identifies most
|
|
396
|
+
formats from the leading bytes, so the format is optional; it is needed for a
|
|
397
|
+
format with no signature to sniff, such as SVG.
|
|
398
|
+
|
|
399
|
+
```ruby
|
|
400
|
+
result = C2PA.read_buffer(data: signed)
|
|
401
|
+
result = C2PA.read_buffer(data: svg_bytes, format: "image/svg+xml")
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Keeping the manifest in a separate file
|
|
405
|
+
|
|
406
|
+
Pass `sidecar:` to write the manifest to its own `.c2pa` file instead of
|
|
407
|
+
embedding it. The output is the asset exactly as it was hashed, which is
|
|
408
|
+
usually byte-for-byte the input. Use this when the file can't be modified, or
|
|
409
|
+
when the manifest is served separately.
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
C2PA.sign(
|
|
413
|
+
file: "photo.jpg",
|
|
414
|
+
output: "published/photo.jpg",
|
|
415
|
+
sidecar: "published/photo.c2pa",
|
|
416
|
+
certificate: "cert.pem",
|
|
417
|
+
key: "key.pem",
|
|
418
|
+
manifest: manifest
|
|
419
|
+
)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
The asset is only valid together with its sidecar. Changing either one, or
|
|
423
|
+
pairing the sidecar with a different file, makes it invalid.
|
|
424
|
+
|
|
425
|
+
When a file has no embedded manifest, `C2PA.read` looks beside it for the same
|
|
426
|
+
name with a `.c2pa` extension, so the pair above reads with no extra argument.
|
|
427
|
+
For a sidecar kept anywhere else, name it:
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
C2PA.read(file: "photo.jpg", manifest_file: "manifests/1234.c2pa")
|
|
431
|
+
C2PA.read_buffer(data: bytes, format: "image/jpeg", manifest_data: sidecar_bytes)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Reading a `.c2pa` file on its own returns the manifest, but it always reports
|
|
435
|
+
`Invalid` with `assertion.dataHash.mismatch` (or the matching BMFF code),
|
|
436
|
+
because there's no asset for the hash to be checked against.
|
|
437
|
+
|
|
336
438
|
### Naming your application
|
|
337
439
|
|
|
338
440
|
Signed files credit `ruby-c2pa` by default. To credit your own application
|
|
@@ -355,8 +457,8 @@ The signed manifest then reads:
|
|
|
355
457
|
{
|
|
356
458
|
"name": "Acme Editor",
|
|
357
459
|
"version": "2.0",
|
|
358
|
-
"org.
|
|
359
|
-
"org.
|
|
460
|
+
"org.contentauth.c2pa_rs": "0.90.22",
|
|
461
|
+
"org.rubygems.ruby_c2pa": "0.6.0"
|
|
360
462
|
}
|
|
361
463
|
```
|
|
362
464
|
|
|
@@ -370,12 +472,15 @@ calling application.
|
|
|
370
472
|
### Checking the SDK version
|
|
371
473
|
|
|
372
474
|
```ruby
|
|
373
|
-
puts C2PA.sdk_version # => "0.
|
|
475
|
+
puts C2PA.sdk_version # => "0.90.22" (the c2pa-rs version the gem was built against)
|
|
374
476
|
```
|
|
375
477
|
|
|
376
478
|
### Error handling
|
|
377
479
|
|
|
378
|
-
All errors inherit from `C2PA::Error`, so you can rescue broadly or narrowly
|
|
480
|
+
All errors inherit from `C2PA::Error`, so you can rescue broadly or narrowly.
|
|
481
|
+
`SigningError` covers signing and the post-signing verification, `ReadError`
|
|
482
|
+
reading, `InvalidManifestError` anything the builder rejects, and
|
|
483
|
+
`InvalidSettingsError` anything `C2PA.configure` cannot use.
|
|
379
484
|
|
|
380
485
|
```ruby
|
|
381
486
|
begin
|
|
@@ -392,6 +497,12 @@ rescue C2PA::ReadError => e
|
|
|
392
497
|
puts "Could not read manifest: #{e.message}"
|
|
393
498
|
end
|
|
394
499
|
|
|
500
|
+
begin
|
|
501
|
+
C2PA.configure { |config| config.trust_anchors = "ca/root.pem" }
|
|
502
|
+
rescue C2PA::InvalidSettingsError => e
|
|
503
|
+
puts "Settings not usable: #{e.message}"
|
|
504
|
+
end
|
|
505
|
+
|
|
395
506
|
# Or rescue any C2PA error broadly
|
|
396
507
|
begin
|
|
397
508
|
C2PA.sign(file: "photo.jpg", output: "photo_signed.jpg", certificate: "cert.pem", key: "key.pem", manifest: manifest)
|
|
@@ -429,6 +540,56 @@ C2PA.configure do |config|
|
|
|
429
540
|
end
|
|
430
541
|
```
|
|
431
542
|
|
|
543
|
+
When fetches stay on, c2pa-rs follows HTTP redirects but refuses one that
|
|
544
|
+
points at an internal address, such as localhost, a private network or a cloud
|
|
545
|
+
metadata endpoint. The URL can come from the asset being read, so this stops a
|
|
546
|
+
crafted file from reaching your internal services. To refuse redirects
|
|
547
|
+
altogether:
|
|
548
|
+
|
|
549
|
+
```ruby
|
|
550
|
+
C2PA.configure do |config|
|
|
551
|
+
config.allow_redirects = false
|
|
552
|
+
end
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Thumbnails
|
|
556
|
+
|
|
557
|
+
c2pa-rs can embed a thumbnail of the asset in its manifest, and of each
|
|
558
|
+
ingredient supplied as a file. Verify tools show it alongside the credentials.
|
|
559
|
+
It is off unless you turn it on:
|
|
560
|
+
|
|
561
|
+
```ruby
|
|
562
|
+
C2PA.configure do |config|
|
|
563
|
+
config.thumbnails = true
|
|
564
|
+
config.thumbnail_size = 512 # longest edge in pixels
|
|
565
|
+
end
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
The default is off because c2pa-rs scales to a fixed long edge and upscales to
|
|
569
|
+
reach it. Its own default is 1024, so a 160×120 image gets a 1024×768
|
|
570
|
+
thumbnail, roughly ten times the size of the asset it describes. Set
|
|
571
|
+
`thumbnail_size` no larger than your assets, or leave thumbnails off for small
|
|
572
|
+
images.
|
|
573
|
+
|
|
574
|
+
Thumbnails are produced for JPEG, PNG, WebP and TIFF. Other formats sign
|
|
575
|
+
without one; c2pa-rs treats that as non-fatal.
|
|
576
|
+
|
|
577
|
+
### Padding in MP4 and other BMFF files
|
|
578
|
+
|
|
579
|
+
MP4, MOV and related formats can hold `free` and `skip` boxes, which are
|
|
580
|
+
padding. Tools often rewrite them after a file is signed, so by default the
|
|
581
|
+
signature leaves them out and those edits don't break it. To make any change
|
|
582
|
+
to the padding break the signature, as a change to the media would:
|
|
583
|
+
|
|
584
|
+
```ruby
|
|
585
|
+
C2PA.configure do |config|
|
|
586
|
+
config.exclude_free_and_skip_boxes = false
|
|
587
|
+
end
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
This only affects signing. The choice is recorded in the signed file, and any
|
|
591
|
+
reader holds the file to it.
|
|
592
|
+
|
|
432
593
|
### Everything configurable
|
|
433
594
|
|
|
434
595
|
| Setting | Default | Purpose |
|
|
@@ -439,10 +600,19 @@ end
|
|
|
439
600
|
| `verify_trust` | `true` | whether trust is checked at all |
|
|
440
601
|
| `remote_manifest_fetch` | `true` | whether reading may fetch over the network |
|
|
441
602
|
| `ocsp_fetch` | `false` | whether revocation is checked over OCSP |
|
|
603
|
+
| `allow_redirects` | `true` | whether network fetches follow redirects; internal targets are always refused |
|
|
604
|
+
| `exclude_free_and_skip_boxes` | `true` | whether BMFF padding boxes are left out of the signature |
|
|
605
|
+
| `thumbnails` | `false` | embed a thumbnail of the asset and of file-backed ingredients |
|
|
606
|
+
| `thumbnail_size` | 1024 | longest edge of the thumbnail, in pixels |
|
|
607
|
+
| `thumbnail_format` | smallest | `:jpeg`, `:png` or `:gif` |
|
|
608
|
+
| `thumbnail_quality` | `:medium` | `:low`, `:medium` or `:high` |
|
|
442
609
|
|
|
443
610
|
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.
|
|
445
|
-
|
|
611
|
+
sent, so anything left alone keeps c2pa-rs's own default. Two exceptions are
|
|
612
|
+
always sent, because this gem's default differs from c2pa-rs's: `thumbnails`,
|
|
613
|
+
and c2pa-rs's own verify-after-sign check, which the gem turns off in favour of
|
|
614
|
+
its `verify:` guard.
|
|
615
|
+
`C2PA.configure` with no block resets everything.
|
|
446
616
|
|
|
447
617
|
Turning `verify_trust` off means nothing is ever reported as untrusted, which
|
|
448
618
|
in a library for establishing provenance is rarely what you want. It exists for
|
|
@@ -465,31 +635,59 @@ signed, read back, and asserted to validate.
|
|
|
465
635
|
| MOV | `video/quicktime` |
|
|
466
636
|
| MP3 | `audio/mpeg` |
|
|
467
637
|
| WAV | `audio/wav` |
|
|
468
|
-
|
|
|
638
|
+
| EPUB | `application/epub+zip` |
|
|
639
|
+
| Word (DOCX) | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` |
|
|
640
|
+
| OpenDocument text (ODT) | `application/vnd.oasis.opendocument.text` |
|
|
641
|
+
| OpenXPS | `application/oxps` |
|
|
642
|
+
| PDF | `application/pdf` |
|
|
469
643
|
|
|
470
644
|
The format is detected automatically from the file extension.
|
|
471
645
|
|
|
646
|
+
### EPUB, Office and OpenDocument files
|
|
647
|
+
|
|
648
|
+
These formats are ZIP packages. The manifest is added as
|
|
649
|
+
`META-INF/content_credential.c2pa`, and every other part of the package is
|
|
650
|
+
covered by the signature, so editing the document, or adding or removing a
|
|
651
|
+
file in it, makes it invalid. The document still opens in its usual app after
|
|
652
|
+
signing.
|
|
653
|
+
|
|
654
|
+
c2pa-rs lists the other Office Open XML and OpenDocument types as well: Excel
|
|
655
|
+
and PowerPoint, their macro-enabled forms, OpenDocument spreadsheets,
|
|
656
|
+
presentations, drawings and templates. They use the same code, but only the
|
|
657
|
+
four above have fixtures in the suite.
|
|
658
|
+
|
|
659
|
+
When reading one of these from memory, pass `format:` to `C2PA.read_buffer`.
|
|
660
|
+
They all begin with the same ZIP header, so c2pa-rs can't identify them from
|
|
661
|
+
the bytes alone.
|
|
662
|
+
|
|
472
663
|
JPEG XL must be in the ISOBMFF container form. A bare codestream has no boxes
|
|
473
664
|
to hold a manifest, and c2pa-rs rejects it.
|
|
474
665
|
|
|
475
|
-
### PDF
|
|
666
|
+
### PDF
|
|
476
667
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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`.
|
|
668
|
+
PDFs sign and read like any other format. The manifest goes into the
|
|
669
|
+
document's associated files, as `/AFRelationship /C2PA_Manifest`, and the file
|
|
670
|
+
still opens in a PDF reader afterwards.
|
|
483
671
|
|
|
484
|
-
|
|
672
|
+
```ruby
|
|
673
|
+
C2PA.sign(
|
|
674
|
+
file: "contract.pdf",
|
|
675
|
+
output: "contract_signed.pdf",
|
|
676
|
+
certificate: "cert.pem",
|
|
677
|
+
key: "key.pem",
|
|
678
|
+
manifest: manifest
|
|
679
|
+
)
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
This is new. c2pa-rs had no PDF writer until 0.91.1 (September 2026):
|
|
683
|
+
`get_writer` returned `None`, every write path returned `NotImplemented`, and
|
|
684
|
+
upstream had closed the request for one in December 2025. Releases of this gem
|
|
685
|
+
up to 0.5.0 said PDF signing was impossible, and while they shipped that was
|
|
686
|
+
true. It is not true now, and 0.6.0 is the first release where a PDF signs.
|
|
485
687
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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.
|
|
688
|
+
Expect the signed file to be much larger than the original when the document
|
|
689
|
+
is small. The manifest carries a full certificate chain, which is a few
|
|
690
|
+
kilobytes whatever the document weighs.
|
|
493
691
|
|
|
494
692
|
### Test fixtures
|
|
495
693
|
|
|
@@ -499,10 +697,11 @@ sources, so they carry no third-party content and no licence obligations. They
|
|
|
499
697
|
are deliberately real files rather than placeholders — 160×120 images with
|
|
500
698
|
actual detail, real audio samples, real video frames, and EXIF metadata on the
|
|
501
699
|
JPEG — because C2PA writes into container structures that an empty file would
|
|
502
|
-
not exercise.
|
|
700
|
+
not exercise. The PDF and the four ZIP-based documents are written by hand in
|
|
701
|
+
the same script. All fifteen total about 68 KB.
|
|
503
702
|
|
|
504
|
-
Regenerating them needs `ffmpeg`, `cjxl` and `
|
|
505
|
-
not.
|
|
703
|
+
Regenerating them needs `ffmpeg`, `cwebp`, `cjxl`, `exiftool` and `zip`; running
|
|
704
|
+
the tests does not.
|
|
506
705
|
|
|
507
706
|
Signing certificates are generated on demand, one chain per key type, so every
|
|
508
707
|
supported algorithm is covered:
|
|
@@ -525,20 +724,35 @@ Ruby (C2PA.sign)
|
|
|
525
724
|
▼
|
|
526
725
|
Rust (C2PA::Native.sign_file)
|
|
527
726
|
│
|
|
528
|
-
│
|
|
727
|
+
│ c2pa-rs Builder, through a shared Context
|
|
529
728
|
▼
|
|
530
729
|
c2pa-rs — embeds signed manifest into the file
|
|
531
730
|
```
|
|
532
731
|
|
|
533
|
-
The Rust extension (`ext/c2pa_native/src/lib.rs`) defines `C2PA::Native` with
|
|
732
|
+
The Rust extension (`ext/c2pa_native/src/lib.rs`) defines `C2PA::Native` with six methods:
|
|
534
733
|
|
|
535
734
|
| Method | Description |
|
|
536
735
|
|--------|-------------|
|
|
537
|
-
| `C2PA::Native.sign_file` | Sign a file and write the result |
|
|
736
|
+
| `C2PA::Native.sign_file` | Sign a file and write the result. Takes the manifest JSON, an optional intent, and any ingredient files |
|
|
737
|
+
| `C2PA::Native.sign_buffer` | The same over bytes: a binary string in, the signed binary string out |
|
|
538
738
|
| `C2PA::Native.read_file` | Read and return the manifest JSON |
|
|
739
|
+
| `C2PA::Native.read_buffer` | The same over bytes, with an optional format hint |
|
|
740
|
+
| `C2PA::Native.configure` | Replace the shared c2pa-rs Context with one built from a settings document |
|
|
539
741
|
| `C2PA::Native.sdk_version` | Return the c2pa-rs version string |
|
|
540
742
|
|
|
541
|
-
|
|
743
|
+
All four run with the global VM lock released, so other Ruby threads are not
|
|
744
|
+
blocked while c2pa-rs hashes and signs. The bytes and paths are copied out of
|
|
745
|
+
Ruby before the lock goes, and the result is turned into a Ruby object after
|
|
746
|
+
it is back; nothing in between touches the interpreter.
|
|
747
|
+
|
|
748
|
+
Signing and reading go through one c2pa-rs `Context`, built once and shared
|
|
749
|
+
across threads. `C2PA.configure` replaces it rather than mutating it, so a
|
|
750
|
+
signing call already in flight keeps the settings it started with.
|
|
751
|
+
|
|
752
|
+
Input validation (missing files, invalid manifests, unreadable ingredient
|
|
753
|
+
files, unusable settings) is handled in Ruby before calling into Rust. Errors
|
|
754
|
+
from the native layer are caught and re-raised as typed `C2PA::Error`
|
|
755
|
+
subclasses.
|
|
542
756
|
|
|
543
757
|
## Contributing
|
|
544
758
|
|