ruby-c2pa 0.2.1 → 0.3.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 +105 -0
- data/CONTRIBUTING.md +117 -0
- data/README.md +154 -8
- data/Rakefile +29 -1
- data/ext/c2pa_native/Cargo.lock +4128 -0
- data/ext/c2pa_native/Cargo.toml +1 -1
- data/ext/c2pa_native/src/lib.rs +25 -3
- data/lib/c2pa/digital_source_types.rb +72 -0
- data/lib/c2pa/manifest.rb +115 -4
- data/lib/c2pa/version.rb +1 -1
- data/lib/c2pa.rb +68 -4
- data/ruby-c2pa.gemspec +28 -3
- metadata +10 -12
- data/ext/c2pa_native/target/release/build/oid-registry-c350c75504c969dc/out/oid_db.rs +0 -540
- data/ext/c2pa_native/target/release/build/pix-f303ab89d734032b/out/gamma_lut.rs +0 -68
- data/ext/c2pa_native/target/release/build/serde-2e9abb4d9d73cae4/out/private.rs +0 -6
- data/ext/c2pa_native/target/release/build/serde-f471616b462b0caf/out/private.rs +0 -6
- data/ext/c2pa_native/target/release/build/serde_core-2b94e9134dc44065/out/private.rs +0 -5
- data/ext/c2pa_native/target/release/build/serde_core-3dc945fa0ab21dd5/out/private.rs +0 -5
- data/ext/c2pa_native/target/release/build/thiserror-a1f4c63469c326b9/out/private.rs +0 -5
- data/ext/c2pa_native/target/release/build/typenum-4eb7d34b9fb695ef/out/tests.rs +0 -20563
- data/ext/c2pa_native/target/release/build/typenum-b8de96984639a942/out/tests.rs +0 -20563
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98842ac83d8c5e7dacdd28d148cc0e0a0997d2b8ad20205e2d4275238992b813
|
|
4
|
+
data.tar.gz: 0dd78073b9b631c901658564b3ce5dc8b80e5e4edaaae6e7a9966df760b0b1c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 05eb4f8d6c31fb6fc6af2facb25c1c3a335fc505b97b46d00c9a846fbc6b72cd5dd0048805fd985f24a615f6a4dc8dde991a089e7d3423256fff63a3cb5566c3
|
|
7
|
+
data.tar.gz: ca81e533198e62ffe4ae92ef7302653191cf3b5dfb14ae1088b878a97c44b5281073b68db2f04d9ab04dc11662e59c7300db7957db99836e677b0774c9f4baad
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.3.0] — 2026-08-25
|
|
11
|
+
|
|
12
|
+
Runs on c2pa-rs 0.90, and fixes every defect found while building a test suite
|
|
13
|
+
that verifies against c2pa-rs rather than against itself.
|
|
14
|
+
|
|
15
|
+
### If you have signed files with an earlier version
|
|
16
|
+
|
|
17
|
+
**Re-sign them.** Assets signed by 0.2.1 and earlier are rejected by current
|
|
18
|
+
verifiers, because their `c2pa.created` action carries no `digitalSourceType`.
|
|
19
|
+
c2pa-rs began requiring one in 0.90, and validation happens when a file is
|
|
20
|
+
read, not when it is written — so files that were valid when signed became
|
|
21
|
+
invalid as verifiers updated around them.
|
|
22
|
+
|
|
23
|
+
Anything signed using the README's editing example never validated at all. See
|
|
24
|
+
`c2pa.opened` under Fixed.
|
|
25
|
+
|
|
26
|
+
### Breaking
|
|
27
|
+
|
|
28
|
+
- `c2pa.created` now requires a `digital_source_type`. c2pa-rs accepts any
|
|
29
|
+
string, so a default would validate while asserting something untrue about an
|
|
30
|
+
asset's origin — a camera original and generative AI output are not
|
|
31
|
+
interchangeable claims. Use `C2PA::DigitalSourceTypes::UNSPECIFIED` when the
|
|
32
|
+
origin is genuinely unknown.
|
|
33
|
+
- `C2PA.sign` now reads the signed file back and raises `C2PA::SigningError` if
|
|
34
|
+
it does not validate, deleting the output. Pass `verify: false` to keep it.
|
|
35
|
+
Code that previously produced invalid files now fails instead of succeeding
|
|
36
|
+
quietly.
|
|
37
|
+
- `add_action(C2PA::Actions::OPENED)` now raises. The action must reference its
|
|
38
|
+
parent ingredient by hashed URI, which cannot be built from Ruby. Pass
|
|
39
|
+
`intent: :edit` to `C2PA::Manifest.new` instead.
|
|
40
|
+
- `c2pa.translated` now requires `sourceLanguage` and `targetLanguage`
|
|
41
|
+
parameters, as RFC 5646 codes.
|
|
42
|
+
- Invalid UTF-8 in a manifest now raises `C2PA::InvalidManifestError` rather
|
|
43
|
+
than `JSON::GeneratorError`, so `rescue C2PA::Error` catches it as the README
|
|
44
|
+
has always claimed.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **Signing a TIFF aborted the Ruby process.** An invalid free in `atree`
|
|
49
|
+
0.5.3, pulled transitively by c2pa-rs 0.78.3. No exception was raised, so it
|
|
50
|
+
could not be rescued; in a web process it took down the worker. Fixed
|
|
51
|
+
upstream in c2pa-rs 0.78.4, one day before 0.2.1 was published.
|
|
52
|
+
- **`c2pa.opened` produced files that never validated.** The README documented
|
|
53
|
+
adding the action directly, which cannot work. Editing is now supported
|
|
54
|
+
through `intent: :edit`, which lets c2pa-rs derive the parent ingredient from
|
|
55
|
+
the source and wire the action to it.
|
|
56
|
+
- **Signed files credited `c2pa-rs` as the claim generator.** They now credit
|
|
57
|
+
`ruby-c2pa`, and an application can name itself with `generator_name:`.
|
|
58
|
+
- **The gemspec homepage pointed at a repository that does not exist**, so the
|
|
59
|
+
link from RubyGems returned 404.
|
|
60
|
+
- **The gem shipped 1.3 MB of Rust build artifacts**, and its contents varied
|
|
61
|
+
with whatever had been compiled on the machine that built it.
|
|
62
|
+
- **`Cargo.lock` was never packaged.** The extension compiles at install time,
|
|
63
|
+
so every installer resolved dependencies afresh — which is how 0.2.1 shipped
|
|
64
|
+
against the broken `atree` even though the repository pinned it.
|
|
65
|
+
- **PDF was advertised as signable.** c2pa-rs has no PDF writer at any version;
|
|
66
|
+
signing one raises, and the documentation now says so.
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- `C2PA::DigitalSourceTypes` — the IPTC vocabulary, plus `UNSPECIFIED` for
|
|
71
|
+
declining to claim an origin rather than guessing.
|
|
72
|
+
- `intent: :edit` on `C2PA::Manifest`, for signing an asset derived from
|
|
73
|
+
another one.
|
|
74
|
+
- `generator_name:` and `generator_version:`, for naming your application as
|
|
75
|
+
the claim generator.
|
|
76
|
+
- `verify:` on `C2PA.sign`, defaulting to `true`.
|
|
77
|
+
- Continuous integration on Linux and macOS. macOS is not redundant: the
|
|
78
|
+
invalid free behind the TIFF crash was surfaced by macOS libmalloc, where
|
|
79
|
+
glibc may corrupt the heap silently.
|
|
80
|
+
- `CONTRIBUTING.md`, recording the rule that every test must be shown to fail
|
|
81
|
+
before it is merged.
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
|
|
85
|
+
- c2pa-rs 0.78.3 → 0.90.15.
|
|
86
|
+
- Verified format support: JPEG, PNG, WebP, TIFF, AVIF, JPEG XL, MP4, MOV, MP3,
|
|
87
|
+
WAV. Each has a fixture and a signing test. MOV, MP3 and JPEG XL were
|
|
88
|
+
previously undocumented; PDF was documented and never worked.
|
|
89
|
+
- All seven signing algorithms are exercised — es256, es384, es512, ps256,
|
|
90
|
+
ps384, ps512, ed25519 — each against a key of the matching type.
|
|
91
|
+
- The test suite went from 10 tests that compared the code to itself to 85 that
|
|
92
|
+
sign real files and assert on what c2pa-rs reads back.
|
|
93
|
+
|
|
94
|
+
## [0.2.1] — 2026-03-17
|
|
95
|
+
|
|
96
|
+
Tagged retroactively. See the v0.2.1 tag for the defects it shipped with.
|
|
97
|
+
|
|
98
|
+
## [0.2.0] — 2026-03-17
|
|
99
|
+
|
|
100
|
+
Tagged retroactively.
|
|
101
|
+
|
|
102
|
+
[Unreleased]: https://github.com/eddorre/ruby-c2pa/compare/v0.3.0...HEAD
|
|
103
|
+
[0.3.0]: https://github.com/eddorre/ruby-c2pa/compare/v0.2.1...v0.3.0
|
|
104
|
+
[0.2.1]: https://github.com/eddorre/ruby-c2pa/compare/v0.2.0...v0.2.1
|
|
105
|
+
[0.2.0]: https://github.com/eddorre/ruby-c2pa/releases/tag/v0.2.0
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Running the tests
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bundle install
|
|
7
|
+
bundle exec rake test
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
That compiles the native extension, generates signing certificates, and runs
|
|
11
|
+
the suite. A clean checkout needs no manual setup and no network access.
|
|
12
|
+
|
|
13
|
+
Regenerating the media fixtures is the one thing that needs extra tooling, and
|
|
14
|
+
only if you are changing them:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
brew install ffmpeg webp jpeg-xl exiftool # macOS
|
|
18
|
+
./test/fixtures/generate.sh
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## The testing rule
|
|
22
|
+
|
|
23
|
+
**Every test must be shown to fail.** Break the code it covers on purpose,
|
|
24
|
+
confirm the test catches it, put the evidence in the pull request.
|
|
25
|
+
|
|
26
|
+
This is not a general principle borrowed from somewhere. It comes from this
|
|
27
|
+
repository's own history. Version 0.2.1 shipped with a green test suite, and
|
|
28
|
+
that suite passed while the gem:
|
|
29
|
+
|
|
30
|
+
- aborted the Ruby process on any TIFF input
|
|
31
|
+
- emitted manifests that modern verifiers reject
|
|
32
|
+
- advertised PDF signing, which c2pa-rs cannot do at any version
|
|
33
|
+
- documented an editing workflow that has never produced a valid file
|
|
34
|
+
|
|
35
|
+
The suite passed because its assertions compared the code to itself:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
assert_equal "c2pa.created", C2PA::Actions::CREATED # a literal equals itself
|
|
39
|
+
assert_equal "c2pa.created", actions[0]["action"] # JSON contains what we put in
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Ten tests, and not one of them could have failed for any of those defects. A
|
|
43
|
+
test that cannot fail is decoration.
|
|
44
|
+
|
|
45
|
+
### How to check
|
|
46
|
+
|
|
47
|
+
Change the implementation so the behaviour under test is wrong, run the suite,
|
|
48
|
+
confirm the right test fails, then restore:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# lib/c2pa/manifest.rb
|
|
52
|
+
"title" => @title.reverse, # deliberately wrong
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
bundle exec ruby -Ilib -Itest test/c2pa_test.rb
|
|
57
|
+
git checkout -- lib/c2pa/manifest.rb
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Then state it in the pull request:
|
|
61
|
+
|
|
62
|
+
> Verified by reversing the title in `Manifest#to_json`: 19 failures, against 0
|
|
63
|
+
> for the unmutated suite.
|
|
64
|
+
|
|
65
|
+
A broad mutation like that trips many tests, which is fine. A narrow one that
|
|
66
|
+
trips exactly the test you just wrote is better evidence, because it shows the
|
|
67
|
+
test is specific as well as present.
|
|
68
|
+
|
|
69
|
+
Mutating the Rust in `ext/c2pa_native/` counts double — it proves the test
|
|
70
|
+
reaches through the FFI boundary rather than stopping at Ruby.
|
|
71
|
+
|
|
72
|
+
### Two traps, both hit while building this suite
|
|
73
|
+
|
|
74
|
+
**A mutation that does not do what you think.** A NUL-handling test appeared to
|
|
75
|
+
survive a mutation that stripped NUL bytes, which would have meant the test was
|
|
76
|
+
worthless. The mutation was wrong — a shell escape that deleted backslash-zero
|
|
77
|
+
rather than an actual NUL. Written correctly, the test failed immediately.
|
|
78
|
+
Prefer editing the file directly over shell substitution, and check the mutated
|
|
79
|
+
source before trusting the result.
|
|
80
|
+
|
|
81
|
+
**A harness that only fails one way.** For tests that assert on an external
|
|
82
|
+
verdict rather than on our own code, mutate in both directions. A harness made
|
|
83
|
+
to report no failures and a harness made to report spurious ones fail different
|
|
84
|
+
tests; checking only one leaves the other half unverified.
|
|
85
|
+
|
|
86
|
+
## Where there is no oracle
|
|
87
|
+
|
|
88
|
+
Most assertions can be checked against c2pa-rs, by signing a file and reading
|
|
89
|
+
back its verdict. Some cannot, and those must say so rather than pretend:
|
|
90
|
+
|
|
91
|
+
- **Action names** — c2pa-rs does not validate them. `acme.nonsense` signs and
|
|
92
|
+
reads back clean, so `C2PA::Actions` can only be checked for duplicates and a
|
|
93
|
+
correct namespace.
|
|
94
|
+
- **Digital source types** — any string is accepted, so the constants are only
|
|
95
|
+
as good as the IPTC vocabulary they were transcribed from.
|
|
96
|
+
|
|
97
|
+
Where a rule is enforced more strictly here than by c2pa-rs, label it as a
|
|
98
|
+
deliberate divergence and add a test that fails when upstream changes its mind.
|
|
99
|
+
The gem may be stricter than the SDK; it may not claim the SDK agrees.
|
|
100
|
+
|
|
101
|
+
## Working on an issue
|
|
102
|
+
|
|
103
|
+
Issues are grouped into sprints by label and tracked against a release
|
|
104
|
+
milestone. Each carries acceptance criteria and a verification command.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git checkout -b fix/42-short-description
|
|
108
|
+
# ...
|
|
109
|
+
gh pr create --milestone "<milestone>" --label "<sprint label>"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Reference the issue with `Closes #42` so it closes on merge. CI runs the suite
|
|
113
|
+
on Linux and macOS for every pull request; both must pass.
|
|
114
|
+
|
|
115
|
+
macOS is not redundant in that matrix. The invalid free behind the TIFF crash
|
|
116
|
+
was surfaced by macOS libmalloc, which aborts on a bad free where glibc may
|
|
117
|
+
corrupt the heap silently.
|
data/README.md
CHANGED
|
@@ -93,18 +93,63 @@ Every signed file requires a `C2PA::Manifest` with at least one action. Actions
|
|
|
93
93
|
require "c2pa"
|
|
94
94
|
|
|
95
95
|
manifest = C2PA::Manifest.new(title: "Sunset over the bay")
|
|
96
|
-
manifest.add_action(
|
|
96
|
+
manifest.add_action(
|
|
97
|
+
C2PA::Actions::CREATED,
|
|
98
|
+
digital_source_type: C2PA::DigitalSourceTypes::DIGITAL_CAPTURE
|
|
99
|
+
)
|
|
97
100
|
```
|
|
98
101
|
|
|
102
|
+
`c2pa.created` must declare how the asset came into being. c2pa-rs rejects a
|
|
103
|
+
manifest without it, and accepts any string you supply — so a wrong value
|
|
104
|
+
validates while asserting something untrue. The gem therefore requires you to
|
|
105
|
+
choose rather than defaulting on your behalf:
|
|
106
|
+
|
|
107
|
+
| Constant | Use for |
|
|
108
|
+
|----------|---------|
|
|
109
|
+
| `DIGITAL_CAPTURE` | a camera original |
|
|
110
|
+
| `TRAINED_ALGORITHMIC_MEDIA` | generative AI output |
|
|
111
|
+
| `COMPOSITE_WITH_TRAINED_ALGORITHMIC_MEDIA` | edited using generative AI |
|
|
112
|
+
| `SCREEN_CAPTURE` | a screenshot |
|
|
113
|
+
| `HUMAN_EDITS` | human-edited media |
|
|
114
|
+
| `UNSPECIFIED` | the origin is genuinely unknown |
|
|
115
|
+
|
|
116
|
+
`C2PA::DigitalSourceTypes::ALL` lists them all. Reach for `UNSPECIFIED` when you
|
|
117
|
+
do not know — it is what c2pa-rs uses in its own fixtures, and it is honest in a
|
|
118
|
+
way that guessing is not.
|
|
119
|
+
|
|
120
|
+
This requirement arrived in c2pa-rs 0.90. Manifests signed by releases before
|
|
121
|
+
0.3.0 omit the field and are rejected by current verifiers.
|
|
122
|
+
|
|
99
123
|
Actions can be chained:
|
|
100
124
|
|
|
101
125
|
```ruby
|
|
102
|
-
manifest = C2PA::Manifest.new(title: "
|
|
103
|
-
.add_action(C2PA::Actions::
|
|
126
|
+
manifest = C2PA::Manifest.new(title: "Sunset over the bay")
|
|
127
|
+
.add_action(C2PA::Actions::CREATED,
|
|
128
|
+
digital_source_type: C2PA::DigitalSourceTypes::DIGITAL_CAPTURE)
|
|
129
|
+
.add_action(C2PA::Actions::PUBLISHED)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Editing an existing asset
|
|
133
|
+
|
|
134
|
+
When the file you are signing derives from another one, declare the intent
|
|
135
|
+
rather than adding `c2pa.opened` yourself:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
manifest = C2PA::Manifest.new(title: "Edited photo", intent: :edit)
|
|
104
139
|
.add_action(C2PA::Actions::EDITED)
|
|
105
140
|
.add_action(C2PA::Actions::PUBLISHED)
|
|
106
141
|
```
|
|
107
142
|
|
|
143
|
+
c2pa-rs derives the parent ingredient from the source file and adds a
|
|
144
|
+
`c2pa.opened` action tied to it, so the signed manifest records
|
|
145
|
+
`c2pa.opened`, `c2pa.edited`, `c2pa.published` and a `parentOf` ingredient.
|
|
146
|
+
|
|
147
|
+
`c2pa.opened` cannot be added by hand. The specification requires it to
|
|
148
|
+
reference its parent ingredient by hashed URI, and that hash is computed over
|
|
149
|
+
the ingredient as c2pa-rs serialises it — so `add_action(C2PA::Actions::OPENED)`
|
|
150
|
+
raises and points here. Earlier releases of this gem documented adding it
|
|
151
|
+
directly; manifests built that way never validated.
|
|
152
|
+
|
|
108
153
|
Each action accepts optional fields from the C2PA specification:
|
|
109
154
|
|
|
110
155
|
```ruby
|
|
@@ -183,7 +228,8 @@ The `output` path must not already exist — `C2PA.sign` will raise a `C2PA::Sig
|
|
|
183
228
|
|
|
184
229
|
```ruby
|
|
185
230
|
manifest = C2PA::Manifest.new(title: "Sunset over the bay")
|
|
186
|
-
.add_action(C2PA::Actions::CREATED
|
|
231
|
+
.add_action(C2PA::Actions::CREATED,
|
|
232
|
+
digital_source_type: C2PA::DigitalSourceTypes::DIGITAL_CAPTURE)
|
|
187
233
|
|
|
188
234
|
C2PA.sign(
|
|
189
235
|
file: "photo.jpg",
|
|
@@ -194,6 +240,28 @@ C2PA.sign(
|
|
|
194
240
|
)
|
|
195
241
|
```
|
|
196
242
|
|
|
243
|
+
`C2PA.sign` reads the signed file back and confirms it validates before
|
|
244
|
+
returning. If it does not, the output file is deleted and a
|
|
245
|
+
`C2PA::SigningError` is raised naming the failure codes.
|
|
246
|
+
|
|
247
|
+
This matters because c2pa-rs applies its rules when *reading*, not when
|
|
248
|
+
writing. Signing reports success for manifests that every verifier rejects,
|
|
249
|
+
which is exactly what earlier versions of this gem did — silently, for months.
|
|
250
|
+
The check costs one extra read of the output.
|
|
251
|
+
|
|
252
|
+
Turn it off with `verify: false` if you want the file kept for inspection:
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
C2PA.sign(
|
|
256
|
+
file: "photo.jpg",
|
|
257
|
+
output: "photo_signed.jpg",
|
|
258
|
+
certificate: "cert.pem",
|
|
259
|
+
key: "key.pem",
|
|
260
|
+
manifest: manifest,
|
|
261
|
+
verify: false
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
197
265
|
Specify a different signing algorithm with `algorithm:` (default is `"es256"`):
|
|
198
266
|
|
|
199
267
|
```ruby
|
|
@@ -214,9 +282,43 @@ result = C2PA.read(file: "photo_signed.jpg")
|
|
|
214
282
|
|
|
215
283
|
active = result["manifests"][result["active_manifest"]]
|
|
216
284
|
puts active["title"]
|
|
217
|
-
puts active["claim_generator_info"].first["name"]
|
|
285
|
+
puts active["claim_generator_info"].first["name"] # => "ruby-c2pa"
|
|
218
286
|
```
|
|
219
287
|
|
|
288
|
+
### Naming your application
|
|
289
|
+
|
|
290
|
+
Signed files credit `ruby-c2pa` by default. To credit your own application
|
|
291
|
+
instead:
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
manifest = C2PA::Manifest.new(
|
|
295
|
+
title: "Sunset over the bay",
|
|
296
|
+
generator_name: "Acme Editor",
|
|
297
|
+
generator_version: "2.0"
|
|
298
|
+
).add_action(
|
|
299
|
+
C2PA::Actions::CREATED,
|
|
300
|
+
digital_source_type: C2PA::DigitalSourceTypes::DIGITAL_CAPTURE
|
|
301
|
+
)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The signed manifest then reads:
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"name": "Acme Editor",
|
|
309
|
+
"version": "2.0",
|
|
310
|
+
"org.rubygems.ruby_c2pa": "0.3.0",
|
|
311
|
+
"org.contentauth.c2pa_rs": "0.78.8"
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
c2pa-rs permits exactly one claim generator entry, so your application replaces
|
|
316
|
+
the gem as the name rather than preceding it. The gem is recorded in a
|
|
317
|
+
namespaced field alongside it, which is how c2pa-rs records itself.
|
|
318
|
+
|
|
319
|
+
Releases before 0.3.0 credited `c2pa-rs` and named neither the gem nor the
|
|
320
|
+
calling application.
|
|
321
|
+
|
|
220
322
|
### Checking the SDK version
|
|
221
323
|
|
|
222
324
|
```ruby
|
|
@@ -252,7 +354,8 @@ end
|
|
|
252
354
|
|
|
253
355
|
## Supported file formats
|
|
254
356
|
|
|
255
|
-
|
|
357
|
+
Each format below has a fixture and a signing test in the suite: the file is
|
|
358
|
+
signed, read back, and asserted to validate.
|
|
256
359
|
|
|
257
360
|
| Format | MIME type |
|
|
258
361
|
|--------|-----------|
|
|
@@ -261,14 +364,50 @@ Signing and reading are supported for any format supported by c2pa-rs, including
|
|
|
261
364
|
| WebP | `image/webp` |
|
|
262
365
|
| TIFF | `image/tiff` |
|
|
263
366
|
| AVIF | `image/avif` |
|
|
264
|
-
|
|
|
367
|
+
| JPEG XL | `image/jxl` |
|
|
368
|
+
| MP4 | `video/mp4` |
|
|
265
369
|
| MOV | `video/quicktime` |
|
|
266
370
|
| MP3 | `audio/mpeg` |
|
|
267
371
|
| WAV | `audio/wav` |
|
|
268
|
-
| PDF | `application/pdf` |
|
|
269
372
|
|
|
270
373
|
The format is detected automatically from the file extension.
|
|
271
374
|
|
|
375
|
+
JPEG XL must be in the ISOBMFF container form. A bare codestream has no boxes
|
|
376
|
+
to hold a manifest, and c2pa-rs rejects it.
|
|
377
|
+
|
|
378
|
+
### Not supported
|
|
379
|
+
|
|
380
|
+
**PDF cannot be signed.** c2pa-rs can read C2PA data out of a PDF but has no
|
|
381
|
+
writer for it — `get_writer` returns `None` and `save_cai_store` returns
|
|
382
|
+
`NotImplemented` — so `C2PA.sign` raises `C2PA::SigningError` with
|
|
383
|
+
`type is unsupported`. This is true at every c2pa-rs version. Earlier releases
|
|
384
|
+
of this gem listed PDF as supported; that was never correct.
|
|
385
|
+
|
|
386
|
+
### Test fixtures
|
|
387
|
+
|
|
388
|
+
The media fixtures are generated by
|
|
389
|
+
[`test/fixtures/generate.sh`](test/fixtures/generate.sh) from ffmpeg's built-in
|
|
390
|
+
sources, so they carry no third-party content and no licence obligations. They
|
|
391
|
+
are deliberately real files rather than placeholders — 160×120 images with
|
|
392
|
+
actual detail, real audio samples, real video frames, and EXIF metadata on the
|
|
393
|
+
JPEG — because C2PA writes into container structures that an empty file would
|
|
394
|
+
not exercise. All ten total 92 KB.
|
|
395
|
+
|
|
396
|
+
Regenerating them needs `ffmpeg`, `cjxl` and `exiftool`; running the tests does
|
|
397
|
+
not.
|
|
398
|
+
|
|
399
|
+
Signing certificates are generated on demand, one chain per key type, so every
|
|
400
|
+
supported algorithm is covered:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
bundle exec rake fixtures:certs
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`rake test` does this for you. The certificates are not committed — they are
|
|
407
|
+
development material, and regenerating costs a fraction of a second. Ruby's
|
|
408
|
+
OpenSSL binding is used rather than the `openssl` command because macOS ships
|
|
409
|
+
LibreSSL, which cannot generate Ed25519 keys.
|
|
410
|
+
|
|
272
411
|
## How it works
|
|
273
412
|
|
|
274
413
|
```
|
|
@@ -293,6 +432,13 @@ The Rust extension (`ext/c2pa_native/src/lib.rs`) defines `C2PA::Native` with th
|
|
|
293
432
|
|
|
294
433
|
Input validation (missing files, invalid manifests) is handled in Ruby before calling into Rust. Errors from the native layer are caught and re-raised as typed `C2PA::Error` subclasses.
|
|
295
434
|
|
|
435
|
+
## Contributing
|
|
436
|
+
|
|
437
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). The short version: every test must be
|
|
438
|
+
shown to fail before it is merged. The suite that shipped with 0.2.1 passed
|
|
439
|
+
while the gem crashed the Ruby process on TIFF input, so a green run is only
|
|
440
|
+
worth what its assertions can catch.
|
|
441
|
+
|
|
296
442
|
## License
|
|
297
443
|
|
|
298
444
|
MIT
|
data/Rakefile
CHANGED
|
@@ -14,5 +14,33 @@ Rake::TestTask.new(:test) do |t|
|
|
|
14
14
|
t.verbose = true
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
# The signing tests need an X.509 certificate chain and key per algorithm.
|
|
18
|
+
# These are generated locally rather than downloaded: it needs no network, it
|
|
19
|
+
# covers every algorithm the gem accepts rather than only ES256, and it keeps
|
|
20
|
+
# third-party material out of the repository entirely.
|
|
21
|
+
#
|
|
22
|
+
# The generated chains are rooted in a CA created here, so signed assets carry
|
|
23
|
+
# a signingCredential.untrusted warning. That does not make a manifest invalid.
|
|
24
|
+
#
|
|
25
|
+
# They are written to an ignored directory rather than committed: a PEM private
|
|
26
|
+
# key in a content-authenticity repository reads badly and trips secret
|
|
27
|
+
# scanning, and regenerating them costs a fraction of a second.
|
|
28
|
+
namespace :fixtures do
|
|
29
|
+
desc "Generate the signing certificates used by the test suite"
|
|
30
|
+
task :certs do
|
|
31
|
+
require_relative "test/fixtures/generate_certs"
|
|
32
|
+
CertificateFixtures.generate_all
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc "Regenerate the signing certificates, replacing any that exist"
|
|
36
|
+
task :certs_force do
|
|
37
|
+
require_relative "test/fixtures/generate_certs"
|
|
38
|
+
CertificateFixtures.generate_all(force: true)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Tests depend on the certificates, so a checkout can run `rake test` with no
|
|
43
|
+
# manual setup, and a missing certificate fails the run rather than quietly
|
|
44
|
+
# skipping the tests that do the real work.
|
|
45
|
+
task test: [:compile, "fixtures:certs"]
|
|
18
46
|
task default: :test
|