
tldr;
Version 0.6.0 of ruby-c2pa can sign PDFs, write manifests to a separate file, and sign EPUB, Word, OpenDocument, and OpenXPS files. Nothing that worked before has changed, so upgrading should be painless.
If you missed it, I released the gem back in May. It’s a Ruby wrapper around the official C2PA Rust library for adding and verifying content credentials on digital media. There were a couple of releases since then (0.4.0 and 0.5.0) that I didn’t write about, but this one has enough in it that I figured it deserved a post.
PDFs, finally
This is the one I’m happiest about. Every single release of the gem up to 0.5.0 had a line in the docs saying that signing PDFs was impossible. That wasn’t me being lazy, I promise. The Rust library that the gem calls out to (c2pa-rs) could read credentials from a PDF but had no way of writing them, and the upstream request to add one had been closed.
Then c2pa-rs 0.91.1 shipped a PDF writer. So now this just works:
manifest = C2PA::Manifest.new(title: "Quarterly Report")
.add_action(
C2PA::Actions::CREATED,
digital_source_type: C2PA::DigitalSourceTypes::DIGITAL_CREATION
)
C2PA.sign(
file: "report.pdf",
output: "signed/report.pdf",
certificate: "cert.pem",
key: "key.pem",
manifest: manifest
)
The manifest ends up in the PDF’s associated files. Getting to delete the “this is impossible” line from the README was pretty satisfying. 😅
Detached manifests
Up until now, signing a file meant embedding the manifest inside of it. That’s fine most of the time, but sometimes you can’t (or don’t want to) modify the original file. Maybe it’s an archival copy or maybe you want to serve the manifest separately.
You can now pass sidecar: and the manifest gets written to its own .c2pa file while the asset is left exactly as it was hashed:
C2PA.sign(
file: "photo.jpg",
output: "published/photo.jpg",
sidecar: "published/photo.c2pa",
certificate: "cert.pem",
key: "key.pem",
manifest: manifest
)
If the sidecar sits next to the asset with the same name, C2PA.read finds it on its own. If you keep it somewhere else, just point at it:
C2PA.read(file: "photo.jpg", manifest_file: "manifests/1234.c2pa")
The two files are a pair. Change either one, or pair the sidecar with a different file, and validation fails. The gem’s verify-after-sign guard checks the pair too and cleans up both files if something’s off.
EPUB, Word, and friends
The gem can now sign and read EPUB, Word (DOCX), OpenDocument text (ODT), and OpenXPS files. Under the hood these are all ZIP packages. The manifest goes in META-INF/content_credential.c2pa and every other file in the package is covered by the signature. The document still opens in whatever app you’d normally use.
Here’s a fun one. c2pa-rs could only read uncompressed ZIP entries, and pretty much no real document is stored that way. So the gem’s native extension now turns on deflate support in its zip dependency to make this work with files you’d actually come across.
One gotcha: if you’re reading one of these from memory with C2PA.read_buffer, you need to pass format:. They all start with the same ZIP header, so there’s no way to tell them apart from the bytes alone.
Some smaller things
There are two new config options. exclude_free_and_skip_boxes lets you include the padding boxes in MP4 and MOV files in the signature, so any change to them breaks it. allow_redirects lets you stop network fetches from following redirects during reading and validation. Both default to whatever c2pa-rs does if you don’t set them.
C2PA.configure do |config|
config.exclude_free_and_skip_boxes = false
config.allow_redirects = false
end
Manifest#add_ingredient also takes a digital_source_type: now, so you can record how an ingredient without its own content credentials was made.
Finally, the gem is built against c2pa-rs 0.91.1, which includes a batch of hardening fixes around parsing XMP, TIFF, and ID3v2 data and validating OCSP responses. The catch is that it needs Rust 1.96 or newer to compile, so you may need to update your toolchain before installing.
Where to find it
As always, it’s on Github and Rubygems. The full list of changes is in the CHANGELOG.
gem install ruby-c2pa