Skip to content

IdentityAssertionSigner produces claimSignature.mismatch on every signed manifest #51

@marmarko

Description

@marmarko

Description

Using IdentityAssertionSigner with CallbackCredentialHolder produces manifests where validation_status always contains claimSignature.mismatch. The identity assertion (cawg.identity) is embedded correctly, but the claim signature is invalid.

Reproduction

Using @contentauth/c2pa-node@0.5.3 on Node.js 24, macOS arm64.

The credential holder callback follows the pattern from IdentityAssertion.spec.js:

import { encode } from 'cbor2';

class TestCawgSigner {
  constructor(manifestSigner) { this.manifestSigner = manifestSigner; }
  sign = async (payload) => {
    const cborBytes = Buffer.from(encode(payload));
    return this.manifestSigner.sign(cborBytes);
  };
}

const cawgSigner = CallbackCredentialHolder.newCallbackCredentialHolder(
  10240, 'cawg.x509.cose', cawgTestSigner.sign
);
const iab = await IdentityAssertionBuilder.identityBuilderForCredentialHolder(cawgSigner);
iab.addReferencedAssertions(['cawg.training-mining']);
const iaSigner = IdentityAssertionSigner.new(c2paSigner.getHandle());
iaSigner.addIdentityAssertion(iab);
await builder.signAsync(iaSigner, source, dest);

Test Matrix

Tested all combinations of directCoseHandling (true/false), with/without ingredients:

directCoseHandling Identity Ingredient cawg.identity present claimSignature.mismatch
true yes no ❌ mismatch
false yes no ❌ mismatch
true yes yes ❌ mismatch
false yes yes ❌ mismatch
false no no n/a ✅ valid
false no yes n/a ✅ valid

Without identity assertion, manifests validate correctly. The issue is 100% reproducible and appears on every call.

Suspected Cause

IdentityAssertionBuilder implements AsyncDynamicAssertion. The content() method calls the credential holder's sign() which is non-deterministic (produces a different signature each time). If content() is called more than once during the signing process (e.g., once for size estimation and once for actual embedding), the claim signature computed over the first result won't match the second result that gets embedded.

Note: The existing IdentityAssertion.spec.js test calls Reader.fromAsset() but does not check validation_status, so this issue would not be caught by the test suite.

Environment

  • @contentauth/c2pa-node: 0.5.3 (also reproduced on 0.5.2)
  • Node.js: 24.14.0
  • Platform: macOS arm64 (Apple Silicon)
  • Certificate: ECDSA P-256 (es256), self-signed with CA chain

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions