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
| Method | C2PA claim | CAWG 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"
}
| Field | Required | Description |
|---|---|---|
alg | Yes | Signing algorithm. One of ps256, ps384, ps512, es256, es384, es512, ed25519. |
sign_cert | Yes | PEM certificate chain, from end-entity certificate to intermediate CA. |
tsa_url | No | URL of a timestamp authority. |
reserve_size | No | Bytes 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:
- Implement
--signer-infoby fetching the certificate from the KMS/HSM/keychain. - Implement signing by sending the stdin bytes to the KMS/HSM/keychain API and writing the returned signature to stdout.
- Handle its own authentication (API tokens, IAM roles, PIN prompts, etc.) internally. C2PA Tool has no involvement in that.
- 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
| Value | Algorithm |
|---|---|
ps256 | RSASSA-PSS with SHA-256 |
ps384 | RSASSA-PSS with SHA-384 |
ps512 | RSASSA-PSS with SHA-512 |
es256 | ECDSA with SHA-256 (default) |
es384 | ECDSA with SHA-384 |
es512 | ECDSA with SHA-512 |
ed25519 | EdDSA |
The algorithm must be compatible with the private key and signing certificate. For more information, see Signing and certificates.