feat: Implement live video section 19.4 — Verifiable Segment Info#2
Open
N1Knight wants to merge 38 commits into
Open
feat: Implement live video section 19.4 — Verifiable Segment Info#2N1Knight wants to merge 38 commits into
N1Knight wants to merge 38 commits into
Conversation
…rtificate (§19.7.3)
…pture - Populate `bmffHash` in `SegmentInfoMap` using SHA-256 over the segment data, excluding VSI emsg boxes (offset=12 per BMFF FullBox layout) - Capture `manifestId` from the signed init segment via `Reader::from_stream` and embed it in subsequent media segments per §19.4 - Add regression tests: bmffHash non-null, manifestId empty without init, manifestId populated after signing init, and c2pa.hash.bmff.v3 label guard
…tId to urn:uuid format
Add `restore_manifest_id_from_signed_init` to `LiveVideoVsiSigner` and update the CLI to skip init re-signing when `--previous-segment` is provided, preserving a consistent `manifestId` across all segments per §19.4.
Per RFC 9052 §4.4 and C2PA §18.25.2, the signer's EE certificate is the detached payload of the signerBinding COSE_Sign1, not the AAD. Replace tbs_data(cert) with tbs_detached_data(cert, b"") in both build_signer_binding and verify_signer_binding so the Sig_Structure is spec-compliant.
…_Key - Serialize signerBinding as CBOR tag 18 (COSE_Sign1_Tagged) instead of wrapping the tagged bytes in a bstr, matching the C2PA spec §18.25.2 and Unified Streaming's implementation. - Add field 3 (alg = -8, EdDSA) to the Ed25519 COSE_Key per RFC 9052. - Add Builder.add_assertion_cbor() to bypass the to_value() pipeline which drops CBOR tags through ValueSerializer. - Add tests for tag 18 wire format, detached payload, protected header algorithm, COSE_Key alg field, and wrong EE cert rejection.
N1Knight
pushed a commit
that referenced
this pull request
Apr 1, 2026
* FLAC support * Moved Architecture.md to docs folder * Update links in Architecture.md to correct paths * Add the correct flac sample * Mark test-only methods as test-only Move the flac asset-handler documentation into docs/formats folder. * refactor: Extract shared ID3v2 logic into `id3_helper` module (#2) * Refactor common code between mp3 and flac * Add more tests for mp3_io * Rename id3_audio to id3_helper --------- Co-authored-by: Gopalakrishna Sharma <1406458+mpgopala@users.noreply.github.com> * Update flac.md with id3_helper details * More refactoring * Fix clippy and fmt errors * Fix clippy errors (Attempt 2) * Fix fmt errors (Attempt 2) * Review comments 1. Remove dependency on metaflac as it was validating the entire flac file. Validating only the initial header is enough. 2. Remove Architecture.md. Will add it in a separate PR. 3. Error handling. * Review comments 1. Remove dependency on metaflac as it was validating the entire flac file. Validating only the initial header is enough. 2. Remove Architecture.md. Will add it in a separate PR. 3. Error handling. * Format fixes * Return OK if the stream has TooManyManifestStores from add_required_frame * Update rustls-webpki version to 0.103.10 to fix vulnerability audit failure * A proper upgrade for rustls-webpki * Removing testing details Removing testing details as it already exists in a single file (flac_io.rs).
…tentauth#1899) * docs: Combine docs on Context and Settings to reduce duplication * Remove file that was deleted in main * Add links to REDME * polish edits * Comments from Nick * Remove toml examples, other small edits
…rtificate (§19.7.3)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](actions/download-artifact@v7...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ntentauth#1913) Bumps [svenstaro/upload-release-action](https://github.com/svenstaro/upload-release-action) from 2.11.3 to 2.11.4. - [Release notes](https://github.com/svenstaro/upload-release-action/releases) - [Changelog](https://github.com/svenstaro/upload-release-action/blob/master/CHANGELOG.md) - [Commits](svenstaro/upload-release-action@2.11.3...2.11.4) --- updated-dependencies: - dependency-name: svenstaro/upload-release-action dependency-version: 2.11.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…pture - Populate `bmffHash` in `SegmentInfoMap` using SHA-256 over the segment data, excluding VSI emsg boxes (offset=12 per BMFF FullBox layout) - Capture `manifestId` from the signed init segment via `Reader::from_stream` and embed it in subsequent media segments per §19.4 - Add regression tests: bmffHash non-null, manifestId empty without init, manifestId populated after signing init, and c2pa.hash.bmff.v3 label guard
…tId to urn:uuid format
Add `restore_manifest_id_from_signed_init` to `LiveVideoVsiSigner` and update the CLI to skip init re-signing when `--previous-segment` is provided, preserving a consistent `manifestId` across all segments per §19.4.
Per RFC 9052 §4.4 and C2PA §18.25.2, the signer's EE certificate is the detached payload of the signerBinding COSE_Sign1, not the AAD. Replace tbs_data(cert) with tbs_detached_data(cert, b"") in both build_signer_binding and verify_signer_binding so the Sig_Structure is spec-compliant.
…_Key - Serialize signerBinding as CBOR tag 18 (COSE_Sign1_Tagged) instead of wrapping the tagged bytes in a bstr, matching the C2PA spec §18.25.2 and Unified Streaming's implementation. - Add field 3 (alg = -8, EdDSA) to the Ed25519 COSE_Key per RFC 9052. - Add Builder.add_assertion_cbor() to bypass the to_value() pipeline which drops CBOR tags through ValueSerializer. - Add tests for tag 18 wire format, detached payload, protected header algorithm, COSE_Key alg field, and wrong EE cert rejection.
…labs/c2pa-rs into feat/live-video-section-19-4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements C2PA Live Video §19.4 — Verifiable Segment Info (VSI), the second validation method for live video streams. Instead of embedding a full C2PA manifest in each segment, the init segment carries a
c2pa.session-keysassertion with one or more session keys (COSE_Key / RFC 9052), and each media segment carries aCOSE_Sign1_Taggedsignature in an ISO BMFFemsgbox.This PR also wires up full VSI signing support using caller‑provided Ed25519 session keys, including incremental signing across process runs via
resume_from_segmentand CLI flags--session-keyand--previous-segment.Also implements §19.7.3
signerBindingverification: each session key'sCOSE_Sign1_TaggedsignerBindingis now cryptographically verified against the manifest signer's end-entity X.509 certificate, ensuring session keys were issued by the same entity that signed the manifest.High-Level Changes
VSI & Session Key Validation (
sdk/src/live_video/): Core logic to validate VSI fields (sequenceNumber,bmffHash,signerBinding), enforce sequence bounds, and verify session keys (including mandatorykidchecks).Cryptographic Bindings (
session_key_validation.rs): ImplementssignerBindingverification. Extracts the SPKI from the end-entity certificate (viax509-parser) and verifies theCOSE_Sign1_Taggedsignature produced by the session key.New Assertions (
sdk/src/assertions/session_keys.rs): Adds typed structs andserdesupport for thec2pa.session-keysassertion.VSI signing from explicit session keys (
sdk/src/live_video/vsi_signing.rs): Replaces the ephemeral key mode withLiveVideoVsiSigner::from_signing_key, which takes a caller‑provided Ed25519 session key and builds a spec‑compliantc2pa.session-keysassertion (includingsignerBinding) into the init segment.Incremental VSI signing (
resume_from_segment): AddsLiveVideoVsiSigner::resume_from_segment, which parses a previously signed VSIemsgbox, reads thesequenceNumber, and setsnext_sequence_number = sequenceNumber + 1so segments can be signed incrementally across separate process runs.CLI wiring for VSI (
cli/src/live_video_sign.rs,cli/src/main.rs): Thelive-video-signsubcommand now requires--session-keywhen--method vsi, always re‑signs the init segment for VSI, and supports--previous-segmentto resume the VSI sequence number from an earlier segment using the same session key.Key design decisions
COSE_Key integer map keys: The
c2pa.session-keysassertion stores keys as rawc2pa_cbor::Valuewith integer CBOR keys (RFC 9052 §7.1). The SDK's manifest read path transcodes CBOR through JSON, converting integer keys to text strings and byte values to integer arrays.cose_key.rshandles both representations transparently.Session key resolution:
validate_verifiable_segment_infofinds the matching session key by comparing thekidin theCOSE_Sign1unprotected header against the keys registered viavalidate_session_keys.signerBindingbytes extraction: The internal CBOR→JSON→CBOR roundtrip (viaManifestAssertion::as_json_object) loses the CBORBytestype.extract_signer_binding_byteshandles all resulting representations (Value::Bytes,Value::Textbase64,Value::Arrayof integers) to support both direct CBOR access and the current JSON-transcoded path.Certificate from manifest: The end-entity certificate for
signerBindingverification is extracted from the manifest's own signature info (Manifest::signature_info().cert_chain()), requiring no additional CLI arguments.bmffHash verification: The
bmffHashfield in the VSISegmentInfoMapis deserialized into the existingBmffHashstruct viac2pa_cbor::value::from_value. Verification reusesBmffHash::verify_in_memory_hash, which internally callsbmff_to_jumbf_exclusionsto resolve/emsgxpath exclusions into byte ranges, then hashes the segment data excluding those ranges. WhenbmffHashisNull, verification is skipped for backward compatibility.Test
cargo test --features="file_io" -p c2pa -- live_videoEnd to end test
The caller provides an Ed25519 session key (
--session-key, raw 32-byte seed). The init segment carries ac2pa.session-keysassertion; each media segment receives aCOSE_Sign1in anemsgbox signed with that session key.Sign all segments at once
cargo run --bin c2patool -- live_video_demo/input live-video-sign \ --segments_glob "seg_*.m4s" \ --output live_video_demo/signed_vsi \ --manifest live_video_demo/manifest.json \ --init init.mp4 \ --method vsi \ --session-key live_video_demo/ed25519_session.keySign segments one at a time (simulating a live ingest pipeline)
First segment:
cargo run --bin c2patool -- live_video_demo/input live-video-sign \ --segments_glob "seg_001.m4s" \ --output live_video_demo/signed_vsi \ --manifest live_video_demo/manifest.json \ --init init.mp4 \ --method vsi \ --session-key live_video_demo/ed25519_session.keySubsequent segments (use
--previous-segmentto resume the sequence number):cargo run --bin c2patool -- live_video_demo/input live-video-sign \ --segments_glob "seg_002.m4s" \ --output live_video_demo/signed_vsi \ --manifest live_video_demo/manifest.json \ --init init.mp4 \ --method vsi \ --session-key live_video_demo/ed25519_session.key \ --previous-segment live_video_demo/signed_vsi/seg_001.m4sVerify all segments
The validator auto-detects the method from the init segment manifest
(
c2pa.session-keysassertion):cargo run --bin c2patool -- live_video_demo/signed_vsi/init.mp4 live-video \ --segments_glob "seg_*.m4s"Verify a single segment
cargo run --bin c2patool -- live_video_demo/signed_vsi/init.mp4 live-video \ --segments_glob "seg_001.m4s"Spec references
c2pa.session-keysassertion