Signing assets with C2PA Tool

August 13, 2026 ยท View on GitHub

C2PA assets may carry two independent signatures:

  • C2PA claim signature (required to create a manifest): identifies the tool or service that created the manifest.
  • CAWG identity assertion (optional): cryptographically binds a named identity (person or organization) to the asset.

Each signature is produced by a separate signer. Both signers are configured independently and may use different keys and certificates.

Important

Private keys in settings files are for development and testing only. In production, use a subprocess signer or a remote signing service so that private key material never passes through C2PA Tool.

Signing options

MethodC2PA claimCAWG identity
Subprocess signer--signer-path--identity-signer-path
Remote signing service[signer.remote] in settings(not yet supported)
Settings with private key (testing only)[signer.local] in settings[cawg_x509_signer.local] in settings
Manifest fields (testing only)sign_cert + private_key in manifest JSONโ€”

Subprocess signer protocol

A subprocess signer is any executable that implements two operations: info and sign. The same protocol applies to both --signer-path and --identity-signer-path.

Info query

Before signing, C2PA Tool calls the subprocess with --signer-info to discover the signing certificate and algorithm. The subprocess must write a JSON object to stdout and exit 0:

{
  "alg": "es256",
  "sign_cert": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
  "tsa_url": "https://timestamp.example.com"
}
FieldRequiredDescription
algYesSigning algorithm. One of ps256, ps384, ps512, es256, es384, es512, ed25519.
sign_certYesPEM certificate chain, from end-entity certificate to intermediate CA.
tsa_urlNoURL of a timestamp authority.
reserve_sizeNoBytes to reserve in the asset for the signature. The signer knows its own maximum signature size. If absent, a default based on the certificate size is used.

If cert and algorithm are already configured in settings (see Signing with settings only below), the info query is skipped and those values are used directly.

Signing

When signing, C2PA Tool writes the bytes to be signed to the subprocess's stdin. The subprocess must write the raw signature bytes to stdout and exit 0.

Error handling

If the subprocess exits with a non-zero status, C2PA Tool treats it as a signing failure and surfaces the subprocess's stderr output in the error message. C2PA Tool does not retry.

If the subprocess exits 0 but writes nothing to stdout, C2PA Tool also returns an error.

Reserve size

C2PA Tool must reserve space in the asset file for the signature before calling the signer. The signer declares how much space it needs by returning reserve_size in the --signer-info response. If the field is absent, C2PA Tool uses a default based on the certificate size.

Warning

Deprecated: When cert and algorithm are supplied via settings rather than --signer-info, C2PA Tool passes --alg and --reserve-size to the subprocess for backwards compatibility. This behavior will be removed in a future release.

Signing with a subprocess signer

C2PA claim signing

Use --signer-path to configure a subprocess signer for the C2PA claim signature:

c2patool image.jpg \
    --manifest manifest.json \
    --signer-path ./my-signer \
    -o signed.jpg

The value of --signer-path is a command string: a binary path optionally followed by arguments. For example:

--signer-path "my-kms-wrapper --profile production"

CAWG identity signing

Use --identity-signer-path to also sign a CAWG identity assertion:

c2patool image.jpg \
    --manifest manifest.json \
    --signer-path ./my-c2pa-signer \
    --identity-signer-path ./my-identity-signer \
    -o signed.jpg

The C2PA and CAWG signers are independent. They may be the same executable or different ones.

Signing with a remote service

Configure a remote signing service in the settings file under [signer.remote]:

[signer.remote]
url = "https://signing.example.com/sign"
alg = "es256"
sign_cert = """-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
"""

C2PA Tool sends a POST request with the bytes to sign as the body and expects the raw signature bytes in the response.

Signing with settings only

For development and testing, you can provide the private key directly in the settings file. Do not use this in production. The private key and signing certificate must be in PEM format. The certificate must contain a PEM certificate chain starting with the end-entity certificate used to sign the claim and ending with the intermediate certificate before the root CA certificate.

C2PA claim

[signer.local]
alg = "es256"
sign_cert = """-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
"""
private_key = """-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
"""
tsa_url = "https://timestamp.digicert.com"

Alternatively, put sign_cert, private_key, and (optionally) alg as fields in the manifest JSON, or set the C2PA_SIGN_CERT and C2PA_PRIVATE_KEY environment variables.

If no signer is configured at all, C2PA Tool uses a built-in test certificate and key from the cli/sample folder. This is only suitable for development.

CAWG identity assertion

[cawg_x509_signer.local]
alg = "es256"
sign_cert = """-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
"""
private_key = """-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
"""
tsa_url = "https://timestamp.digicert.com"
referenced_assertions = ["c2pa.actions"]
roles = ["creator"]

If [cawg_x509_signer] is absent, no CAWG identity assertion is generated.

An example settings file is in the cli/tests/fixtures folder.

Writing your own signer

A signer is any executable that implements the two-operation protocol described above. It does not need to be written in Rust or have any knowledge of C2PA internals.

A minimal signer in shell (for illustration only, not for production):

#!/bin/sh
if [ "\$1" = "--signer-info" ]; then
    echo '{"alg":"es256","sign_cert":"-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n"}'
    exit 0
fi
# Read bytes from stdin, sign them, write raw signature to stdout
openssl dgst -sha256 -sign my-key.pem

In practice, a production signer would:

  1. Implement --signer-info by fetching the certificate from the KMS/HSM/keychain.
  2. Implement signing by sending the stdin bytes to the KMS/HSM/keychain API and writing the returned signature to stdout.
  3. Handle its own authentication (API tokens, IAM roles, PIN prompts, etc.) internally. C2PA Tool has no involvement in that.
  4. Exit non-zero and write a diagnostic to stderr on failure.

The signer is responsible for all key management. C2PA Tool only sees the public certificate (from --signer-info) and the resulting signature bytes.

Supported algorithms

ValueAlgorithm
ps256RSASSA-PSS with SHA-256
ps384RSASSA-PSS with SHA-384
ps512RSASSA-PSS with SHA-512
es256ECDSA with SHA-256 (default)
es384ECDSA with SHA-384
es512ECDSA with SHA-512
ed25519EdDSA

The algorithm must be compatible with the private key and signing certificate. For more information, see Signing and certificates.