Skip to content

claimSignature.mismatch when signing with any self-issued certificate via Signer.from_callback — only the official test-cert bundle validates #319

Description

@drorelia36

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:

  1. 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)
  2. Same KMS key, DER signature converted to raw 64-byte r||s before returning from the callback
  3. A local EC key + certificate generated via plain openssl req -x509 (self-signed)
  4. 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
  5. Leaf-only certificate (no root appended), matching the exact shape of the working test bundle
  6. 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?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions