-
Notifications
You must be signed in to change notification settings - Fork 4
Description
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