2.10

Reading and signing manifests with c2patool

The Content Authenticity Initiative's command-line tool is the most direct way to see what a manifest actually contains, and the quickest way to produce one for testing. It is not a consumer verifier, and its defaults assume you know that.

C2PA Tool, invoked as c2patool, is the open-source command-line utility published by the Content Authenticity Initiative for working with C2PA manifests. It does three things: prints a summary of the manifests in a file as JSON, prints a lower-level report of the manifest data, and adds a new signed manifest to a file. It is built on the CAI's Rust SDK, the same library behind the Verify website, so when a web tool gives a result you want to examine closely, c2patool shows you the underlying data.

Every command on this page was checked against the tool's official documentation in the contentauth/c2patool repository and run against version 0.28.0, released on 22 September 2026. The tool is still pre-1.0 and its options change between releases; when in doubt, c2patool -h is authoritative for the version you have installed.

Where c2patool lives now

The tool's home has moved more than once, which confuses search results and older tutorials. It started as a standalone repository, was later folded into the c2pa-rs monorepo alongside the Rust library, and now lives in its own repository again at github.com/contentauth/c2patool. Older releases are tagged c2patool-vX.Y.Z on the c2pa-rs releases page; newer ones are published on the c2patool repository. Some of the official examples still show file paths from the monorepo period, such as c2pa-rs/cli/sample.

The names are easy to mix up:

Installing

On macOS, Homebrew packages the tool, though the formula can trail the latest release by a few versions:

brew install c2patool

On any platform, download the archive for your operating system from the releases page (prebuilt binaries are provided for macOS, Windows, and x86-64 Linux), extract it, and put the c2patool executable on your PATH. On macOS you may need to clear Gatekeeper's quarantine flag before an unnotarized binary will run. With a Rust toolchain installed, you can build from source instead:

cargo install c2patool

Confirm the install and check the version:

c2patool -V
c2patool -h

Reading a manifest

The basic syntax is c2patool <ASSET_PATH> [OPTIONS] [SUBCOMMAND]. With no options, the tool reads the file and prints the manifest store as JSON:

c2patool photo.jpg

The output, abbreviated here from one of the tool's own sample images, has a stable top-level shape:

{
  "active_manifest": "contentauth:urn:uuid:5a08e472-...",
  "manifests": {
    "contentauth:urn:uuid:5a08e472-...": {
      "claim_generator": "make_test_images/0.12.0 c2pa-rs/0.12.0",
      "title": "C.jpg",
      "format": "image/jpeg",
      "assertions": [
        { "label": "stds.schema-org.CreativeWork", "data": { ... } },
        { "label": "c2pa.actions.v2",
          "data": { "actions": [ { "action": "c2pa.created" },
                                 { "action": "c2pa.drawing", ... } ] } }
      ],
      "signature_info": {
        "alg": "Ps256",
        "issuer": "C2PA Test Signing Cert",
        "time": "2022-08-19T19:03:41+00:00"
      }
    }
  },
  "validation_status": [
    { "code": "signingCredential.untrusted", ... }
  ],
  "validation_results": { "activeManifest": { "success": [...],
                          "informational": [...], "failure": [...] } },
  "validation_state": "Valid"
}

The fields to look at first:

Other read modes

CommandWhat it shows
c2patool photo.jpg --infoA short report: provenance URI, manifest store size, number of manifests, and any validation issues
c2patool photo.jpg -dThe detailed report (--detailed): the manifest in its internal C2PA form, including the claim, assertion hashes, and hashed URIs
c2patool photo.jpg --treeA text tree of the manifest store: assertions and nested ingredients
c2patool photo.jpg --certsThe PEM certificate chain from the active manifest's signature
c2patool photo.jpg -o ./reportWrites the manifest data, including thumbnails, into a directory

The detailed report is the one to use alongside the manifest structure page: it shows what was actually signed rather than the SDK's friendlier summary. Piping --certs into openssl x509 -text -noout prints the signing certificate's subject, issuer, and validity dates.

Trust: why everything says "untrusted"

Out of the box, c2patool checks structure and cryptography but has no trust list configured, so it reports signingCredential.untrusted for every signer while still giving a validation_state of Valid when the cryptography checks out. To evaluate trust the way consumer tools do, point it at a trust list with the trust subcommand. For the official C2PA trust list:

c2patool photo.jpg trust \
  --trust_anchors 'https://raw.githubusercontent.com/c2pa-org/conformance-public/refs/heads/main/trust-list/C2PA-TRUST-LIST.pem'

The subcommand also accepts --allowed_list (specific end-entity certificates), --trust_config (allowed extended key usages), and --trust_list_uri; each has an environment-variable equivalent such as C2PATOOL_TRUST_ANCHORS. Rather than repeating these on every run, c2patool init trust downloads the official list once and caches it next to the settings file (by default under ~/.config/c2pa/), after which trust checks are applied automatically. Adding --legacy also fetches the frozen interim trust list that the Verify website still uses. The trust list page covers the difference between the two lists.

Adding a manifest

To sign, you describe the manifest in a JSON manifest definition and pass it with -m, naming the output with -o. A minimal definition that records two edits:

{
  "claim_generator_info": [
    { "name": "imgkey-example", "version": "1.0" }
  ],
  "title": "harbor.jpg",
  "assertions": [
    {
      "label": "c2pa.actions.v2",
      "data": {
        "actions": [
          { "action": "c2pa.color_adjustments" },
          { "action": "c2pa.cropped" }
        ]
      }
    }
  ]
}
c2patool harbor.jpg -m manifest.json -o harbor-signed.jpg

With no key or certificate specified, the tool signs with a built-in ES256 test certificate and prints a note that this is only valid for development. The result validates cryptographically but will never be trusted by any consumer tool, which is exactly what you want for testing. The JSON format is documented in the manifest definition docs and the CAI's JSON manifest reference.

Manifest intent

By default the tool applies the edit intent: it adds the source file as a parent ingredient and inserts a c2pa.opened action ahead of the actions you listed. Two flags change this:

A manifest definition can also be passed inline with -c, which is convenient for the create case:

c2patool capture.jpg -c '{"assertions":[]}' --create digitalCapture -o capture-signed.jpg

Ingredients

Ingredients are the earlier assets a new file was made from, and their own manifests are carried inside the new one. There are two ways to add them. -p (--parent) names a parent file that differs from the source:

c2patool edited.jpg -m manifest.json -p original.jpg -o edited-signed.jpg

Additional component ingredients can be listed in the manifest definition with the tool-specific ingredient_paths field, for example "ingredient_paths": ["logo.png"]. Running --tree on the result shows each ingredient and, where it had one, its own manifest nested beneath it. The --ingredient option (c2patool file.jpg --ingredient --output ./ingredient) writes an ingredient definition and a binary .c2pa manifest store for a file, useful for inspecting or reusing its provenance.

Real keys

For anything beyond testing, supply a certificate and key. The tool-specific fields alg, private_key, sign_cert, and ta_url (a timestamp authority) can go in the manifest definition, or the equivalents can go in a settings file or the C2PA_SIGN_CERT and C2PA_PRIVATE_KEY environment variables. The documentation is explicit that keys in files are for development only. In production, use --signer-path, which delegates signing to an external program (a wrapper around a KMS or HSM, for instance) so the private key never passes through c2patool, or configure a remote signing service. The signing documentation specifies the protocol. A certificate that will be trusted by consumer tools has to chain to the C2PA trust list; see the trust list page.

Other outputs

-s (--sidecar) writes the manifest to a separate .c2pa file instead of embedding it, and -r <url> (--remote) embeds a reference to a manifest hosted at that URL. --external-manifest <file.c2pa> validates an asset against a sidecar. The fragment subcommand handles fragmented MP4 (BMFF) video.

Common errors

MessageCause
Error: No claim foundThe file has no embedded C2PA manifest. As with every tool, this says nothing about whether the image is authentic.
Error: Output already exists; use -f/force to force writeThe -o path exists. Add -f to overwrite. If output and source are the same path, the source is overwritten.
Error: Output type must match source typeThe output extension differs from the input. c2patool does not convert formats.
Error: Unsupported file type or type is unsupportedThe format is not one the SDK can embed into, or a path points at something other than a supported asset. Check the supported formats list.
signingCredential.untrustedNot an error in itself. The signer is not on a configured trust list; with no trust list configured, every signer is reported this way.
assertion.dataHash.mismatchThe file's bytes changed after signing, so the hard binding fails and validation_state becomes Invalid. Editing EXIF or XMP with another tool after signing is enough to cause it.
In practice Keep c2patool's full JSON output, not just a screenshot of a web verifier, whenever a provenance result matters to a decision. It records the exact status codes, the certificate issuer, and the SDK's spec version, which is what another examiner needs to reproduce your finding.