open-standards/wai/live-conformance/c2pa-sdk-identity/README.md
Transaction Science 59feeeff98
Some checks failed
WAI conformance / does this change touch it (push) Has been skipped
WAI conformance / cargo-class corpora · linux (push) Has been skipped
WAI conformance / cargo-class corpora · linux-arm64 (push) Has been skipped
WAI conformance / cargo-class corpora · macos (push) Has been skipped
WAI conformance / cargo-class corpora · windows (push) Has been skipped
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (push) Has been skipped
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (push) Has been skipped
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (push) Has been skipped
WAI conformance / v2 vocabulary · manifests validate as stated (push) Has been skipped
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (push) Has been skipped
WAI conformance / independent verifiers · every verifier step, each corpus's Rust side (push) Has been skipped
WAI conformance / each feature of the independent verifiers' steps compiles alone (push) Has been skipped
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (push) Has been skipped
WAI conformance / cross-arch byte-equality · linux (push) Has been skipped
WAI conformance / cross-arch byte-equality · linux-arm64 (push) Has been skipped
WAI conformance / cross-arch byte-equality · macos (push) Has been skipped
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (push) Has been skipped
WAI conformance / C2PA manifest emission · signed, read back, validated (push) Has been skipped
WAI conformance / independent verifiers · each needs= is what a run without packages shows (nightly) (push) Has been skipped
wai-audit collector and player integration / does this change touch it (push) Successful in 1m23s
wai-pack / wai-pack · does this change touch it (push) Successful in 1m26s
wai-fleet service / does this change touch it (push) Successful in 1m28s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 1m29s
WAI operation catalogue / does this change touch it (push) Successful in 1m29s
wai-quantum for JavaScript / does this change touch it (push) Successful in 1m28s
joule-moq-http live HTTP egress / joule-moq-http · does this change touch it (push) Successful in 1m34s
wai-ledger service / does this change touch it (push) Successful in 1m30s
wai-quantum studio and desktop / does this change touch it (push) Successful in 45s
wai-sink-swift native sink package / does this change touch it (push) Successful in 43s
wai-sink-android native sink library / does this change touch it (push) Successful in 45s
WAI Studio / does this change touch it (push) Successful in 43s
wai-app and Create / wai-app · does this change touch it (push) Successful in 5m4s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m10s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 5m35s
joule-moq-agent AI participants / joule-moq-agent · does this change touch it (push) Successful in 6m6s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 5m54s
wai-fleet service / service · macos (push) Successful in 42s
wai-pack / wai-pack · the floor through a media prober (push) Successful in 1m12s
wai-ledger service / service · macos (push) Successful in 1m24s
wai-pack / wai-pack · linux-arm64 (push) Successful in 4m46s
wai-pack / wai-pack · dependency licences (push) Successful in 31s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 5m25s
joule-moq-http live HTTP egress / joule-moq-http, every target (push) Successful in 4m11s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 3m2s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 11m35s
joule-moq-agent AI participants / joule-moq-agent, every target (push) Successful in 3m57s
wai-pack / wai-pack · macos (push) Successful in 10m52s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 14s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 3m34s
WAI examples / WAI examples · does this change touch them (push) Successful in 21s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 10m18s
WAI conformance / a push to main · corpus provenance and sidecars, signer ids, the registry export (push) Successful in 1m11s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 8m51s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 14m44s
wai-fleet service / service · linux (push) Successful in 4m14s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 52s
wai-pack / wai-pack · linux (push) Successful in 5m17s
wai-audit collector and player integration / collector · linux (push) Successful in 6m1s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 20m38s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 17m33s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 2m15s
wai-ledger service / service · linux (push) Successful in 3m58s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 15m28s
wai-pack / wai-pack · independent verifier (Python) (push) Successful in 25m9s
wai-quantum for JavaScript / javascript · linux (push) Successful in 9m42s
wai-quantum studio and desktop / studio · browser (push) Successful in 9m44s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 13m26s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 12m14s
WAI examples / WAI examples · android-app · linux-arm64 (push) Successful in 13m17s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Successful in 18m24s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 4m23s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 37m2s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 12m43s
WAI examples / WAI examples · portable · linux (push) Successful in 18m37s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 12m41s
WAI Studio / Studio in WebKit · linux (push) Failing after 2h30m4s
wai-rs: jxl_encode and the C2PA identity signer on jpegxl-rs 0.16 and c2pa 0.92, and the SDK identity corpus signed again at 0.92.1
The 2026-11-14 wai-conformance nightly (run 35860) passed every job but
"codec wrappers" and "C2PA manifest emission", both compile errors after
526554191 moved jpegxl-rs 0.14->0.16 and c2pa 0.91->0.92 in paths no
per-push job builds:
- codecs::image::jxl_encode and tests/media_decode_bounds.rs: jpegxl-rs 0.16's
  encode takes the pixel type alone and returns the bytes.
- c2pa_emit::identity's signer wrapper: c2pa 0.92's Signer passes a
  &Context to send_timestamp_request; the wrapper passes it on.
- live-conformance/c2pa-sdk-identity signed again with 0.92.1, as its README
  says it is when the lockfile moves the SDK: all 20 vectors keep 0.91.11's
  validator reports, instant and anchors; SDK_VERSION, the README and D6 say
  0.92.1. The 21 corpus records and SDK_VERSION are acknowledged breaks.

Checked on build hosts: the codec wrappers' five test binaries, the image
codec unit tests, the semantic path, wai_prov and clippy (macOS arm64);
the emitter, session and identity tests, the SDK identity vectors and
clippy (Linux aarch64); wai breaking: no unacknowledged break.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-14 07:02:48 -05:00

118 lines
8.7 KiB
Markdown

# live-conformance/c2pa-sdk-identity — identity assertions in manifests a C2PA SDK signed
The corpus of [`extensions/c2pa-binding.md`](../../extensions/c2pa-binding.md) §9.3: PNG files a
C2PA SDK signed, each with the manifest [energy-binding](../../extensions/energy-binding.md) §6
emits and the Creator Assertions Working Group's identity assertions as §4.11 has a WAI tool
write them through the SDK: the assertion of §4.10, written by the writer of WAI's own stores, in
the placeholder the SDK reserves.
**Pinned, not regenerated.** The SDK draws a manifest's identifier (`urn:c2pa:` and a version 4
UUID), a 16-byte salt for every assertion and the parent ingredient's `instanceID` from the
system's random source. Every hash over an assertion, the actions' reference to the ingredient,
the identity assertion's payload and both signatures follow from those, so two signings of one
input differ in bytes and agree in length and in everything a verdict reads. The files here were
written by `wai_c2pa_sdk_identity_vectors gen` with the SDK at the version `wai-rs`'s lockfile
holds (the `c2pa` crate, version 0.92.1, which every claim names in its `claim_generator_info`),
and `vectors.json` pins each by its SHA-256. They are written again when the lockfile moves the
SDK, and only then: on 2026-11-12, from 0.91.7 to 0.91.11, and on 2026-11-14, from 0.91.11 to
0.92.1. Running `gen` again writes another
corpus with the same outcomes, and is not how this one is checked.
**Files.** `vectors.json` holds `time` (the instant *T*), `identityAnchors` (the identity trust
anchors, each a certificate's DER in base64), `claimAnchors` (the claim signer's anchor, which
only a C2PA validator needs), `sdk` (the SDK whose validator's reports are pinned, and its
version) and the vectors: each its `name`, `about`, `asset` (the signed PNG), `sha256` (its
pin), `referenced` (`created`, or `created_or_gathered` for a verifier configured to admit the
claim's gathered assertions), `expect`, and `sdk` (what that SDK's validator reports of the
file when it holds both anchors: its validation `state` and its `failure` codes, sorted). Every
key and certificate is derived from fixed seeds, and none is a key anyone holds. The claim
signer is an Ed25519 certificate for `c2pa-kp-claimSigning` and `id-kp-emailProtection` under
the corpus's claim anchor; a validator that does not hold that anchor reports
`signingCredential.untrusted` for every file, and one that does not hold the identity anchor
`cawg.x509.credential.untrusted` for every identity assertion.
**Equivalence.** A conforming verifier given a vector's asset, the anchors and *T* reaches the
outcome `expect` states (§4.11): for `refuse`, the `reason` of §7 and the code §7 gives it
(`c2pa`); for `accept`, each identity assertion's `label`, `roles`, `actor` (the SHA-256 of its
end-entity certificate), `referenced` (the labels of the assertions it references, in order),
`anchor` (the SHA-256 of the anchor its chain reached) and `signed` (which serialization of its
payload the signature is over: `schema`, the members in the schema's order, which a writer
signs by default, or `deterministic`, the core deterministic encoding; §4.10).
**The vectors.**
- Good: `creator` (over the hard binding, the actions and the energy binding, role
`cawg.creator`), `creator-and-publisher` (two identity assertions, `cawg.identity` and
`cawg.identity__1`), `hard-binding-alone` (no role), `creator-deterministic` (the creator's
assertion signed over the core deterministic encoding, the form a writer may ask for),
`sdk-builder-written` (the creator's assertion written and signed by the SDK's own identity
builder, nothing of it this profile's: its members are in the schema's order in the store and
it holds a `pad2`), and `gathered-reference-admitted` (the file of `gathered-reference`, read
by a verifier that admits `gathered_assertions`).
- Refused, each a manifest the SDK signed around one defect: `carried-from-another-manifest`
and `gathered-reference` (`identity_assertion_mismatch`), `hard-binding-missing`,
`assertion-twice`, `pad-nonzero`, `pad1-missing` (`identity_malformed`), `sig-type-unknown`,
`algorithm-unsupported`, `signed-by-another-key`, `signed-third-order` (the creator's
signature over the payload's members in the order `sig_type`, `role`,
`referenced_assertions`, which is neither serialization) and `signed-other-members` (the
creator's signature over a payload with the publisher's role, beside a payload that states
the creator's) (`identity_signature_mismatch`, each), `credential-untrusted`, `credential-expired` and `credential-wrong-purpose`
(`identity_credential_invalid`). Every reason §7.2 gives an identity assertion refuses one.
**Checking it.**
```
cargo run --no-default-features --features c2pa_emit,c2pa_envelope --bin wai_c2pa_sdk_identity_vectors -- verify live-conformance/c2pa-sdk-identity
python3 -I live-conformance/c2pa_sdk_identity_verify.py live-conformance/c2pa-sdk-identity
```
The first holds every file to its pin and its outcome, has the SDK validate every file's claim
signature and hard binding (`claimSignature.validated`, `assertion.dataHash.match`, and no
failure of the claim, its hashed URIs or its data hash), holds the SDK's validator, configured
with the corpus's two anchors, to the report `vectors.json` pins for each file and to accepting
whole every manifest whose identity assertions are signed by default, and signs every vector
again to the same outcomes and the same reports. The second is the second implementation, written from the extension's text: it
reads the PNG, the store and the claim itself, verifies each identity assertion with the
verifier `c2pa_live_verify.py` holds WAI's own stores to, requires every mutated copy of a good
vector to be refused, and drops each of its checks in turn and requires a vector or a mutated
copy to catch it.
**What the SDK's own validator reports.** The SDK reads identity assertions itself. Of the
corpus's files, on 2026-11-14, at the version `vectors.json` names (0.91.11 reported the same of
every vector of the files it signed, on 2026-11-12, and 0.91.7 of its own on 2026-11-11), configured with the
corpus's claim anchor and identity anchor (its `manifest` and `cawg` trust kinds), as each
vector's `sdk` pins it:
- **the manifests this profile writes by default are accepted whole**: for `creator`,
`creator-and-publisher`, `hard-binding-alone` and `sdk-builder-written` its state is
`Trusted`, it reports no failure code, and for each identity assertion it reports
`cawg.x509.credential.trusted`, `cawg.x509.signature.validated` and
`cawg.identity.well-formed`;
- for `creator-deterministic` it reports `cawg.x509.signature.mismatch` and nothing else, with
the credential trusted. Identity Assertion 1.3 §8.2.1 has the holder sign the
`signer_payload` in RFC 8949 §4.2.1's core deterministic encoding, whose map keys are in the
bytewise order of their encodings (`role`, `sig_type`, `referenced_assertions`). The SDK does
not verify those bytes: it verifies a signature over its own serialization of the payload's
members in the order the schema lists them (`referenced_assertions`, `sig_type`, `role`),
and its builder signs the same. That is the clause the deployed validators depart from, and
it is why §4.10 signs the schema's order by default and keeps §8.2.1's encoding as a form a
writer may ask for ([decisions](../../decisions.md) D6). This profile's verifier accepts
both and states which;
- it reports the code this profile does for `carried-from-another-manifest`
(`cawg.identity.assertion.mismatch`), `hard-binding-missing`, `assertion-twice`,
`pad-nonzero`, `sig-type-unknown`, `credential-untrusted`, and for `signed-by-another-key`,
`signed-third-order` and `signed-other-members` (`cawg.x509.signature.mismatch`, each);
`cawg.x509.signature.outside_validity` with `cawg.x509.credential.untrusted` for
`credential-expired`; `assertion.required.missing` for `pad1-missing`; and
`cawg.x509.credential.invalid` for `algorithm-unsupported`;
- it reports no failure for `credential-wrong-purpose` (it accepts as an identity credential a
certificate whose only purpose is claim signing, which §4.10's profile refuses,
`identity_credential_invalid`) or for `gathered-reference` (it looks for a referenced
assertion in `gathered_assertions` as well, which this profile does only where its verifier
is configured to).
The tests `the_sdks_validator_accepts_the_identity_assertions_this_profile_writes`,
`the_sdks_validator_accepts_the_stores_the_store_writer_writes` (the stores of `c2pa-identity`,
beside their envelopes) and `the_sdks_validator_departs_from_8_2_1_and_reports_a_mismatch_of_its_bytes`
of `c2pa_emit::identity_corpus` hold the first two points, and every vector's `sdk` pin the
rest, so an SDK whose validator reports otherwise is noticed.