@nettrek/c2pa-web-crypto
v0.1.1
Published
WebCrypto-based C2PA reader/verifier for fragmented BMFF (HLS/DASH) — a dependency-free alternative to the @contentauth/c2pa-web WASM core for the VOD reader surface.
Readme
@nettrek/c2pa-web-crypto
A dependency-free, WebCrypto-based C2PA reader/verifier for fragmented BMFF (HLS/DASH) and standalone BMFF assets — no WASM, no worker, verification runs in-thread on the Web Crypto API.
⚠️ Early release. The verification surface is extensively tested (see below) but is not yet hardened for general use, and APIs and behavior may change. Remaining differences vs the reference implementation are tracked in
test/fixtures/KNOWN-DELTAS.md; they are fail-closed (we reject what the reference accepts, not the reverse).
Purpose & scope
This library is a drop-in replacement for @contentauth/c2pa-web
(the WASM C2PA core) — but only for the verification / reading surface that
@nettrek/c2pa-hls-bridge consumes:
createC2pa() → sdk.reader.fromBlob(...) / sdk.reader.fromBlobFragment(...) → c2pa-types ManifestStoreIt reads a C2PA manifest store out of an asset, verifies it (signatures, certificate chain/profile,
timestamps, hard-binding + assertion hashes, CAWG identity, revocation), and produces a
@contentauth/c2pa-types ManifestStore
with a validation_state of Valid / Invalid / Trusted.
It is verification-only. It does not sign, embed, build, or otherwise produce C2PA
manifests — there is no claim generator / signer here. For creating manifests, use c2pa-rs or the
official tooling. The WASM implementation remains the path for anything outside the reader surface.
What it verifies
- COSE_Sign1 claim signatures (ECDSA P-256/384/521, RSASSA-PSS, Ed25519) via WebCrypto.
- X.509 certificate profile (§14.5.1.1) and RFC 5280 §6 trust-chain building to configured anchors.
- RFC 3161 timestamps (
sigTst/sigTst2) and their effect on certificate-validity reference time. - BMFF hard binding: fragmented Merkle (
c2pa.hash.bmff.v2/v3) and standalone whole-file hashes, including nested-path / subset / qualifier exclusions (§18.6). - Every claim-referenced assertion's hashed-URI (§15.10), structural rules (§15.10.1 / §15.11),
and CAWG identity assertions (
cawg.x509.coseCOSE signature — required — plusreferenced_assertionsbinding andpad1/pad2zero-fill). - Stapled OCSP revocation (§15.9.1).
It is also hardened against malformed input: fromBlob/fromBlobFragment never throw — adversarial,
truncated, or corrupt assets degrade to an Invalid result or a null reader.
Known limitations & not-in-scope for the preview
This is a verification-only reader (no signing/embedding). Beyond that, the preview does not
guarantee the following; each is documented with rationale in STATUS.md / test/fixtures/KNOWN-DELTAS.md:
- Online revocation (OCSP §15.9.2) and
did:webCAWG issuer resolution — not performed (network/privacy; a product decision). Stapled OCSP (§15.9.1) anddid:jwkare in scope. - CAWG ICA credential signature (
cawg.identity_claims_aggregation) — surfaced unverified (issuer-key resolution deferred). Thecawg.x509.coseform is verified. - v2
validation_resultssuccess/informational codes — not populated (the flatvalidation_statusfailure list is; the arrays are not consumed by the bridge/player). - Assertion-specific & manifest-type semantic validation — the per-assertion-type validators (actions/ingredient cross-checks, cloud-data, session-keys, time-stamp/cert-status assertion fallbacks, …), update-manifest-as-active content binding, the redaction claim-signature-hash method, and multiple/compressed manifest-store handling are not implemented. Byte-level tampering of any assertion is still caught by the hashed-URI / hard-binding checks.
- Scope of BMFF hashing — fragmented Merkle (
c2pa.hash.bmff.v2/v3) and standalone whole-file BMFF; monolithic Merkle (§15.12.2.1) and multi-asset (§18.9) are not. Non-BMFF hard bindings (c2pa.hash.data/boxes/collection.data) as the active binding are reported non-matching. - Documented spec-parity calls — TSA PKCS#1-v1.5 acceptance, SHA-1 messageImprint reject,
c2pa.hash.collection.datain the hard-binding set,exact:falseBMFF-flag matching. SeeKNOWN-DELTAS.md.
Current state, deliberate spec deviations, and remaining work are tracked in the repository:
STATUS.md, test/fixtures/KNOWN-DELTAS.md, and plans/ (not shipped in the npm package).
Test-suite is the source of truth (npm test).
Usage
import { createC2pa } from '@nettrek/c2pa-web-crypto'
const c2pa = await createC2pa() // trust-off (default)
// fragmented (HLS/DASH): init segment + one media fragment
const reader = await c2pa.reader.fromBlobFragment('video/mp4', initBlob, fragmentBlob)
// standalone (whole file):
// const reader = await c2pa.reader.fromBlob('video/mp4', blob)
const store = await reader?.manifestStore()
console.log(store?.validation_state) // 'Valid' | 'Invalid' | 'Trusted'Trust verification (chain-to-anchor + allow-list, and CAWG identity trust) is opt-in via
Settings:
const c2pa = await createC2pa({ settings: {
trust: { trustAnchors, trustConfig, allowedList }, // C2PA signer trust list
cawgTrust: { trustAnchors, trustConfig, allowedList }, // CAWG identity trust list
verify: { verifyTrust: true }
}})Specifications
- C2PA 2.4 — the primary specification. spec.c2pa.org/…/2.4
- CAWG Identity Assertion — creator-identity assertions (
cawg.identity). cawg.io/identity - JUMBF — ISO/IEC 19566-5 (JPEG Universal Metadata Box Format), the manifest-store container.
- ISO BMFF — ISO/IEC 14496-12 (ISO Base Media File Format), the box structure.
The C2PA 2.4 and CAWG 1.2 text is vendored verbatim under specs/ — the section +
line-number citations in the source comments (e.g. §18.6.1 (line 5246)) refer to those files, so
the references stay valid without an external checkout. See specs/README.md.
Cryptography / encoding RFCs
| RFC | Used for | |---|---| | RFC 9052 / 8152 | COSE (COSE_Sign1 claim signatures) | | RFC 8230 | RSA (RSASSA-PSS) algorithms for COSE | | RFC 8949 | CBOR (claims, assertions, COSE) | | RFC 5280 | X.509 PKI certificate & path validation | | RFC 8017 | PKCS #1 (RSA signatures, RSASSA-PSS params) | | RFC 5652 | CMS (timestamp token / OCSP structures) | | RFC 3161 | Time-Stamp Protocol (TSA tokens) | | RFC 6960 | OCSP (stapled revocation) |
Reference implementations & trust data
- contentauth/c2pa-rs — the authoritative Rust implementation; our BMFF/Merkle hashing and validation logic are cross-checked against it. The CAWG untrusted-differentiation behavior we mirror is issue #2247.
- contentauth/c2pa-js —
@contentauth/c2pa-web(the WASM reader this library replaces) and@contentauth/c2pa-types(theManifestStoretypes). - C2PA trust list — the default timestamp-trust anchors are the self-signed roots of the
official list: contentcredentials.org/trust
(regenerate the bundled subset with
npm run gen:roots).
Test assets
- c2pa-org/public-testfiles — the conformance
catalog (
samples.jsonwithexpectedFailureCodes), spanning conformant + non-conformant assets. - contentauth.github.io/example-assets — Content Authenticity example assets.
- Content Credentials Verify — reference verifier for cross-checking a given asset's expected result.
All test fixtures — media, provenance, and expectations — are committed under test/fixtures/
so the suite is self-contained and does not depend on upstream availability (several fixtures
were captured from ephemeral URLs that no longer resolve). See test/fixtures/RECON.md
(fixture format + Merkle scheme), test/fixtures/PARITY.md, and
test/fixtures/conformance/README.md for provenance and regeneration commands.
Development
npm test # the hermetic parity + regression suite (source of truth)
npm run typecheck
npm run build # emit dist/ (consumed by the bridge)
npm run gen:roots # regenerate src/x509/c2paTrustAnchors.ts from the trust list
npm run test:trust-freshness # opt-in, network: assert the pinned trust list still matches upstreamA one-off recon/debug TS script can be run without a build via node test/_run.mjs <file.ts>
(esbuild-bundles it so it can import src/*.ts directly).
License
MIT © netTrek GmbH & Co. KG
