Sign Fragmented Files

Signing of fragmented (fmp4, DASH, HLS) files utilizes the ClaimGeneratorForFragmentedMediaBuilder and ClaimGeneratorForFragmentedMedia objects. These objects are almost identical to the ClaimGeneratorBuilder and ClaimGenerator objects, but the workflow differs from that described in Signing. The set_media_data() function call is replaced by a call to set_initialization_segment() and the client must also call hash_fragment() and insert_into_fragment() for each fragment of the media file, and at the appropriate times in the signing process.

NOTE: This process applies to a fragmented MP4 file where the media fragments are spread across multiple files. For fmp4 files where all the fragments are in the same file ("flat" fragmented files) the standard Signing procedure should be used.

Call Flow Overview

Here is a sample call flow for signing a fragmented file:

#include "C2PA/C2PAFactory.hpp"

// Create a ClaimGeneratorForFragmentedMediaBuilder
auto builder = 
  c2pa::C2PAFactory::new_claim_generator_for_fragmented_media_builder();

builder
  // Provide claim information
  ->set_media_title("movie.mp4")

  // Provide signature information
  ->set_algorithm(common::SignatureAlgorithm::PS256)
  ->set_certificate(certificate_der)

  // Provide initialization segment data
  ->set_initialization_segment(
    initializaion_segment_asset,
    number_of_fragments
  )

  // Hash the fragments in preparation for signing
  ->hash_fragment(fragment_1_asset)
  ->hash_fragment(fragment_2_asset)
  ...
  ->hash_fragment(fragment_n_asset);

// Build the ClaimGeneratorForFragmentedMedia
auto cg = builder->build();
  
// Provide a timestamp
cg->set_tsa_response(
  post_tsa_request(cg->get_tsa_request())
);

auto to_be_signed_data = cg->get_to_be_signed();

// Insert the C2PA metadata into each fragment
auto output_fragment_1_asset = cg->insert_into_fragment(fragment_1_asset);
auto output_fragment_2_asset = cg->insert_into_fragment(fragment_2_asset);
...
auto output_fragment_n_asset = cg->insert_into_fragment(fragment_n_asset);

// Perform the signature operation
auto output_initialization_segment_asset = cg->sign_media(
  sign_with_rsa_pss(to_be_signed_data)
);

Order of operations

In order to sign a fragmented file, the following steps must be performed in order:

  1. Create a ClaimGeneratorForFragmentedMediaBuilder instance (required)
  2. Provide claim information (optional)
  3. Provide assertions (optional)
  4. Provide signature information (required)
  5. Provide media data
    1. Initialization segment media data (required)
    2. Fragment media data (required)
    3. Ingredient media data (optional)
  6. Build the ClaimGeneratorForFragmentedMedia (required)
  7. Provide a timestamp (optional)
  8. Get the to-be-signed structure (required)
  9. Insert C2PA metadata into the fragment files
  10. Sign the initialization segment

Provide Media Data

Instead of the media data that is provided for monolithic media files via the set_media_data() method, fragmented files require that the client provide an initialization segment and then provide each of the fragments, one-at-a-time, and in the proper order.

set_initialization_segment()

Required

This is a method of the ClaimGeneratorForFragmentedMediaBuilder object. The C2PA manifest store will be embedded into the initialization segment. This C2PA manifest contains a set of reference hashes that all the fragment hashes will be compared against.

The set_initialization_segment() method provides the initialization segment file as an Asset object. The modified initialization segment will be returned from a subsequent call to the sign_media() method.

  • c2pa::ReadWriteAssetPtr initialization_segment - An Asset object representing the raw data of the initialization segment file.
  • uint32_t num_fragment_files - Informs the library of the number of fragments that will be processed. This is required in order to allocate enough space in the C2PA manifest for the proper number of reference hashes.
cg->set_initialization_segment(initialization_segment_asset, 31);

hash_fragment()

Required

This is a method of the ClaimGeneratorForFragmentedMediaBuilder object. It should be called multiple times, once for each fragment file. It will hash the fragments and store the hashes. They will be used to compute the reference hashes placed into the signed manifest. The fragment data is not stored, and it is not modified in any way as part of this function call.

  • c2pa::ReadOnlyAssetPtr fragment - An Asset object representing the raw data of the fragment file.
cg->hash_fragment(fragment_asset);

Insert C2PA Metadata Into the Fragment Files

This is a method of the ClaimGeneratorForFragmentedMedia object. After the hashes have all been calculated, and before the signing process is completed, the C2PA metadata (Merkle tree hashes) needs to be written to each fragment file.

insert_into_fragment()

Required

This should be called multiple times, once for each fragment file. It will add a C2PA box to the fragment.

NOTE: Calls to insert_into_fragment() must be performed after a call to get_to_be_signed() and before a call to sign_media().

  • c2pa::ReadWriteAssetPtr fragment - An Asset object representing the raw data of the fragment file.
auto output_fragment_asset = cg->insert_into_fragment(fragment_asset);

Signing Multiple Files Together Into a Set

There are times when it makes sense to sign multiple files together in a "set" that can be validated as a complete group. An example of this would be when working with adaptive-bitrate files served via DASH or HLS. This is supported in libc2pa.

Call flow

Here's a sample call flow for signing multiple fragmented files together into a set:

#include "C2PA/C2PAFactory.hpp"

// Create a ClaimGeneratorForFragmentedMediaBuilder for rendition 1
auto r1_builder = 
  c2pa::C2PAFactory::new_claim_generator_for_fragmented_media_builder();

r1_builder
  // Provide claim information
  ->set_media_title("movie.mp4")

  // Provide signature information
  ->set_algorithm(common::SignatureAlgorithm::PS256)
  ->set_certificate(certificate_der)

  // Provide rendition 1 initialization segment data
  ->set_initialization_segment(
    r1_initializaion_segment_asset,
    r1_number_of_fragments
  )

  // Hash the fragments in preparation for signing
  ->hash_fragment(r1_fragment_1_asset)
  ->hash_fragment(r1_fragment_2_asset)
  ...
  ->hash_fragment(r1_fragment_n_asset);

// Create a ClaimGeneratorForFragmentedMediaBuilder for rendition 2
auto r2_builder = 
  c2pa::C2PAFactory::new_claim_generator_for_fragmented_media_builder();

r2_builder
  // Provide claim information
  ->set_media_title("movie.mp4")

  // Provide signature information
  ->set_algorithm(common::SignatureAlgorithm::PS256)
  ->set_certificate(certificate_der)

  // Provide rendition 2 initialization segment data
  ->set_initialization_segment(
    r2_initializaion_segment_asset,
    r2_number_of_fragments
  )

  // Hash the fragments in preparation for signing
  ->hash_fragment(r2_fragment_1_asset)
  ->hash_fragment(r2_fragment_2_asset)
  ...
  ->hash_fragment(r2_fragment_n_asset);

// Add the two files together into a "set"
auto msb = c2pa::C2PAFactory::new_media_set_builder();
auto r1_id = msb->add_builder(std::move(r1_builder));
auto r2_id = msb->add_builder(std::move(r2_builder));

// Build the ClaimGeneratorForFragmentedMedia objects
auto cg_map = msb->build();
auto& r1_cg = cg_map[r1_id];
auto& r2_cg = cg_map[r2_id];
  
// Provide a timestamp
r1_cg->set_tsa_response(
  post_tsa_request(r1_cg->get_tsa_request())
);
r2_cg->set_tsa_response(
  post_tsa_request(r2_cg->get_tsa_request())
);

auto r1_to_be_signed_data = r1_cg->get_to_be_signed();
auto r2_to_be_signed_data = r2_cg->get_to_be_signed();

// Insert the C2PA metadata into each fragment
auto r1_output_fragment_1_asset = r1_cg->insert_into_fragment(r1_fragment_1_asset);
auto r1_output_fragment_2_asset = r1_cg->insert_into_fragment(r1_fragment_2_asset);
...
auto r1_output_fragment_n_asset = r1_cg->insert_into_fragment(r1_fragment_n_asset);

auto r2_output_fragment_1_asset = r2_cg->insert_into_fragment(r2_fragment_1_asset);
auto r2_output_fragment_2_asset = r2_cg->insert_into_fragment(r2_fragment_2_asset);
...
auto r2_output_fragment_n_asset = r2_cg->insert_into_fragment(r2_fragment_n_asset);

// Perform the signature operation
auto r1_output_initialization_segment_asset = r1_cg->sign_media(
  sign_with_rsa_pss(r1_to_be_signed_data)
);
auto r2_output_initialization_segment_asset = r2_cg->sign_media(
  sign_with_rsa_pss(r2_to_be_signed_data)
);

The MediaSetBuilder object

The MediaSetBuilder object is a way of combining multiple signed media assets together into a single "set". The hard bindings (Merkle tree hashes) for all files in the set will be included in the initialization segment of each asset.

Creating a MediaSetBuilder object

auto msb = c2pa::C2PAFactory::new_media_set_builder();

add_builder()

The builder objects should be added to the set once they are ready for the build() step. Instead of calling the build() method on each builder, the builders are all built by the MediaSetBuilder. When adding a builder, it needs to be moved (with std::move) into the add_builder() method. After this, the object is no longer valid in its original scope, and can't be used anymore.

Will return a unique_id for the media file.

  • std::unique_ptr<c2pa::ClaimGeneratorForFragmentedMediaBuilder>&& builder - The builder to add to the set.
  • uint32_t unique_id - (Optional) To be used as an identifier in the C2PA Claim. If not provided, then an ID will be generated automatically.
msb->add_builder(std::move(builder));

build()

Will call build() on all the builder objects in the set and return a std::map<uint32_t, c2pa::ClaimGeneratorForFragmentedMedia> that maps unique_ids to claim generators.

auto cg_map = msb->build();