ruby-c2pa 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41362e47552927fd69a0bec7203dc35f59442af7eeaeb1d5f787216e08c18b79
4
- data.tar.gz: 6e93a632b24a7baddf4ba4792d8a9f8043559fd5c168df1a685b8094a73f9689
3
+ metadata.gz: fd571866dd99e9811e93072f9e642625f844a418bc7878d437905231b5cda679
4
+ data.tar.gz: 8854aaaf99e78cce57e4ba8f60102cf64e9aeda243c17b1395878dedb83eb904
5
5
  SHA512:
6
- metadata.gz: b9d9de8da30c033cc5e64ea2d27609ce8be1a9b3d08c400c98f4a092c9ad85f27a985f347477ecd3b019366cf75f9c9c65d17a40f94ce1f82476b753b5dc4a85
7
- data.tar.gz: d888086e87c7eec8fd09d5f8fadb4f6e5f9493d3ecc7199264c10c38bfc254aead1e380d881bbc3b9ad3c7569e30220f57764e85bebe1f98bd0f1a8b816c4e54
6
+ metadata.gz: a3839eadfa75e5d7f3bd2eb22164cec8301195a4fdb7eee7d39cccafdf8abc67fa09b855ece3a43a0bcc5a6e3dddcb408bc1c4f5c73fa0ba5097fd51623384e1
7
+ data.tar.gz: d84bd7979e4a3b14f7039ca3e0f5c3b3cf4b35f663d333cd8d246d52afcbc910602d7a462e5bf5dd22d32b5be3fa603a29f224bc84a848cfd13da46b81bb66cc
data/CHANGELOG.md CHANGED
@@ -7,6 +7,59 @@ 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
+
10
63
  ## [0.5.0] — 2026-09-12
11
64
 
12
65
  Adds capability on top of 0.4.0. Nothing is removed and no existing call
@@ -183,7 +236,8 @@ Tagged retroactively. See the v0.2.1 tag for the defects it shipped with.
183
236
 
184
237
  Tagged retroactively.
185
238
 
186
- [Unreleased]: https://github.com/eddorre/ruby-c2pa/compare/v0.5.0...HEAD
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
187
241
  [0.5.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.4.0...v0.5.0
188
242
  [0.4.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.3.0...v0.4.0
189
243
  [0.3.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.2.1...v0.3.0
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 cannot do at any version
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:
@@ -75,7 +75,7 @@ test is specific as well as present.
75
75
  Mutating the Rust in `ext/c2pa_native/` counts double — it proves the test
76
76
  reaches through the FFI boundary rather than stopping at Ruby.
77
77
 
78
- ### Three traps, all hit while building this suite
78
+ ### Four traps, all hit while building this suite
79
79
 
80
80
  **A mutation that does not do what you think.** A NUL-handling test appeared to
81
81
  survive a mutation that stripped NUL bytes, which would have meant the test was
@@ -100,6 +100,15 @@ that works measures CPU parallelism, which waiting cannot inflate. When a
100
100
  timing test passes under the mutation it was written to catch, suspect the
101
101
  probe before the code.
102
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
+
103
112
  ## Where there is no oracle
104
113
 
105
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
@@ -382,6 +401,40 @@ result = C2PA.read_buffer(data: signed)
382
401
  result = C2PA.read_buffer(data: svg_bytes, format: "image/svg+xml")
383
402
  ```
384
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
+
385
438
  ### Naming your application
386
439
 
387
440
  Signed files credit `ruby-c2pa` by default. To credit your own application
@@ -405,7 +458,7 @@ The signed manifest then reads:
405
458
  "name": "Acme Editor",
406
459
  "version": "2.0",
407
460
  "org.contentauth.c2pa_rs": "0.90.22",
408
- "org.rubygems.ruby_c2pa": "0.5.0"
461
+ "org.rubygems.ruby_c2pa": "0.6.0"
409
462
  }
410
463
  ```
411
464
 
@@ -487,6 +540,18 @@ C2PA.configure do |config|
487
540
  end
488
541
  ```
489
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
+
490
555
  ### Thumbnails
491
556
 
492
557
  c2pa-rs can embed a thumbnail of the asset in its manifest, and of each
@@ -509,6 +574,22 @@ images.
509
574
  Thumbnails are produced for JPEG, PNG, WebP and TIFF. Other formats sign
510
575
  without one; c2pa-rs treats that as non-fatal.
511
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
+
512
593
  ### Everything configurable
513
594
 
514
595
  | Setting | Default | Purpose |
@@ -519,14 +600,18 @@ without one; c2pa-rs treats that as non-fatal.
519
600
  | `verify_trust` | `true` | whether trust is checked at all |
520
601
  | `remote_manifest_fetch` | `true` | whether reading may fetch over the network |
521
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 |
522
605
  | `thumbnails` | `false` | embed a thumbnail of the asset and of file-backed ingredients |
523
606
  | `thumbnail_size` | 1024 | longest edge of the thumbnail, in pixels |
524
607
  | `thumbnail_format` | smallest | `:jpeg`, `:png` or `:gif` |
525
608
  | `thumbnail_quality` | `:medium` | `:low`, `:medium` or `:high` |
526
609
 
527
610
  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.
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.
530
615
  `C2PA.configure` with no block resets everything.
531
616
 
532
617
  Turning `verify_trust` off means nothing is ever reported as untrusted, which
@@ -550,31 +635,59 @@ signed, read back, and asserted to validate.
550
635
  | MOV | `video/quicktime` |
551
636
  | MP3 | `audio/mpeg` |
552
637
  | WAV | `audio/wav` |
553
- | PDF | `application/pdf` | read only, see below |
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` |
554
643
 
555
644
  The format is detected automatically from the file extension.
556
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
+
557
663
  JPEG XL must be in the ISOBMFF container form. A bare codestream has no boxes
558
664
  to hold a manifest, and c2pa-rs rejects it.
559
665
 
560
- ### PDF is read-only
666
+ ### PDF
667
+
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.
561
671
 
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`.
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
+ ```
568
681
 
569
- Earlier releases of this gem listed PDF as signable. That was never correct.
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.
570
687
 
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.
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.
578
691
 
579
692
  ### Test fixtures
580
693
 
@@ -584,10 +697,11 @@ sources, so they carry no third-party content and no licence obligations. They
584
697
  are deliberately real files rather than placeholders — 160×120 images with
585
698
  actual detail, real audio samples, real video frames, and EXIF metadata on the
586
699
  JPEG — because C2PA writes into container structures that an empty file would
587
- not exercise. All ten total 92 KB.
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.
588
702
 
589
- Regenerating them needs `ffmpeg`, `cjxl` and `exiftool`; running the tests does
590
- not.
703
+ Regenerating them needs `ffmpeg`, `cwebp`, `cjxl`, `exiftool` and `zip`; running
704
+ the tests does not.
591
705
 
592
706
  Signing certificates are generated on demand, one chain per key type, so every
593
707
  supported algorithm is covered: