Describe the bug
Every certificate we generate ourselves (self-signed, or a proper 2-cert chain via a local root CA, built multiple ways) fails claim signature validation with claimSignature.mismatch when used via Signer.from_callback() — even when the certificate structure closely matches the one official certificate that does work (the C2PA Conformance Test Root bundle from c2pa-conformulator). We've exhausted the most likely causes and can't identify what's actually different.
To Reproduce
What works — signing with the official test certificate bundle (downloaded from the c2pa-conformulator "Test Certificates" feature — a combined PEM with cert + EC private key, issued by "C2PA Conformance Test Root") via Signer.from_callback() + Builder.sign_file():
signer = c2pa.Signer.from_callback(sign_callback, c2pa.C2paSigningAlg.ES256, certs_pem, tsa_url)
with c2pa.Builder(manifest_json) as builder:
builder.sign_file(input_path, output_path, signer)
Result: validation_state: "Valid", only informational "untrusted" warnings (expected, since it's a test cert).
What fails, identically, every time — tried each of the following as a substitute certificate/key:
- A KMS-backed EC key (GCP Cloud KMS, EC_SIGN_P256_SHA256) + a self-signed certificate we assembled manually via asn1crypto (public key fetched from KMS, TBS bytes signed via KMS asymmetric_sign)
- Same KMS key, DER signature converted to raw 64-byte r||s before returning from the callback
- A local EC key + certificate generated via plain
openssl req -x509 (self-signed)
- A local EC key + a proper 2-certificate chain (root CA + leaf signed by that root via
openssl x509 -req -CA ...), matching the pattern documented in contentauth/c2pa-python-example
- Leaf-only certificate (no root appended), matching the exact shape of the working test bundle
- Both
Builder.sign_file() and Builder.sign() (stream-based, matching c2pa-python-example's pattern exactly)
Every one of these produces:
{
"code": "claimSignature.mismatch",
"explanation": "claim signature is not valid"
}
while all hash/assertion bindings (assertion.hashedURI.match, assertion.dataHash.match) succeed — only the claim signature itself fails.
What we ruled out:
- Signature math: independently verified — KMS-produced signatures verify correctly against the fetched public key using cryptography's own verify(), entirely outside c2pa.
- Cert/key correspondence: confirmed the certificate's embedded public key exactly matches the signing key's public key in every case.
- Signature encoding: tried both DER (as returned natively by KMS/cryptography) and manually converted raw 64-byte r||s — no difference in outcome.
- Certificate extensions: matched BasicConstraints(CA:FALSE), KeyUsage (tried both with and without nonRepudiation, matched exactly to the working cert's "Digital Signature" only), ExtendedKeyUsage (matched exactly to 1.3.6.1.5.5.7.3.36, the only EKU present in the working cert).
- Chain vs. leaf-only: tried both.
- reserve_size() mismatch: confirmed reserve_size() scales proportionally with certificate size and comfortably exceeds actual signature length in all cases; not a truncation issue.
Attachments
Comparison of working vs. non-working certs:
Working (official test bundle):
subject=CN=C2PA Conformance Test Signing, O=C2PA Test, C=US
issuer=CN=C2PA Conformance Test Root, O=C2PA Test, C=US
Key Usage: critical, Digital Signature
Extended Key Usage: 1.3.6.1.5.5.7.3.36
Our best-matched attempt (still fails):
subject=CN=Test Leaf Signer
issuer=CN=Test Root CA
Key Usage: critical, Digital Signature
Extended Key Usage: 1.3.6.1.5.5.7.3.36
Visually near-identical, yet one validates and the other doesn't. Happy to provide full reproduction scripts if useful.
Expected behavior
A properly-formed certificate (matching all visible X.509 requirements of the working test cert) used with Signer.from_callback() should produce a valid claim signature, not claimSignature.mismatch.
Desktop (please complete the following information):
- OS: macOS
- SDK version: c2pa-python 0.90.15
Additional context
Is there a specific, non-obvious requirement for certificates used with Signer.from_callback() (e.g., a specific certificate policy OID, a specific timestamping requirement, a specific way the private key/cert pair must be generated) that isn't covered by matching the visible X.509 fields?
Describe the bug
Every certificate we generate ourselves (self-signed, or a proper 2-cert chain via a local root CA, built multiple ways) fails claim signature validation with
claimSignature.mismatchwhen used viaSigner.from_callback()— even when the certificate structure closely matches the one official certificate that does work (the C2PA Conformance Test Root bundle from c2pa-conformulator). We've exhausted the most likely causes and can't identify what's actually different.To Reproduce
What works — signing with the official test certificate bundle (downloaded from the c2pa-conformulator "Test Certificates" feature — a combined PEM with cert + EC private key, issued by "C2PA Conformance Test Root") via
Signer.from_callback()+Builder.sign_file():Result:
validation_state: "Valid", only informational "untrusted" warnings (expected, since it's a test cert).What fails, identically, every time — tried each of the following as a substitute certificate/key:
openssl req -x509(self-signed)openssl x509 -req -CA ...), matching the pattern documented in contentauth/c2pa-python-exampleBuilder.sign_file()andBuilder.sign()(stream-based, matching c2pa-python-example's pattern exactly)Every one of these produces:
{ "code": "claimSignature.mismatch", "explanation": "claim signature is not valid" }while all hash/assertion bindings (assertion.hashedURI.match, assertion.dataHash.match) succeed — only the claim signature itself fails.
What we ruled out:
Attachments
Comparison of working vs. non-working certs:
Working (official test bundle):
subject=CN=C2PA Conformance Test Signing, O=C2PA Test, C=US
issuer=CN=C2PA Conformance Test Root, O=C2PA Test, C=US
Key Usage: critical, Digital Signature
Extended Key Usage: 1.3.6.1.5.5.7.3.36
Our best-matched attempt (still fails):
subject=CN=Test Leaf Signer
issuer=CN=Test Root CA
Key Usage: critical, Digital Signature
Extended Key Usage: 1.3.6.1.5.5.7.3.36
Visually near-identical, yet one validates and the other doesn't. Happy to provide full reproduction scripts if useful.
Expected behavior
A properly-formed certificate (matching all visible X.509 requirements of the working test cert) used with
Signer.from_callback()should produce a valid claim signature, notclaimSignature.mismatch.Desktop (please complete the following information):
Additional context
Is there a specific, non-obvious requirement for certificates used with
Signer.from_callback()(e.g., a specific certificate policy OID, a specific timestamping requirement, a specific way the private key/cert pair must be generated) that isn't covered by matching the visible X.509 fields?