Signing

Signing a media file is the process of adding a C2PA Manifest, with the accompanying Assertions, Claim, and Signature, to the metadata of that media file. In libc2pa, this is done using the ClaimGenerator class.

Instances of ClaimGenerator are constructed from the ClaimGeneratorBuilder class. The ClaimGeneratorBuilder is prepared with information about the Claim and then used to "build" the ClaimGenerator instance that will perform the actual Claim creation and signing operations.

Create a ClaimGeneratorBuilder Instance

Instances of the ClaimGeneratorBuilder are created with the C2PAFactory:

#include "C2PA/C2PAFactory.hpp"

void main() {
  // Create a ClaimGeneratorBuider
  auto builder = c2pa::C2PAFactory::new_claim_generator_builder(
    // no options specified yields default behavior
  );
}

The returned instance is wrapped in a std::unique_ptr, so it will be automatically destroyed when they go out of scope.

If you need the ClaimGeneratorBuilder to persist beyond the scope in which it was created, you can do so by using the move_unique_to_shared() utility function to move it to a std::shared_ptr. For example:

#include "common/common_utils.hpp"

auto cgb_shared = common_utils::move_unique_to_shared(cgb);

Customizing GenerationOptions

The ClaimGenerator's behavior may optionally be customized with a GenerationOptions structure, which is passed in when creating the ClaimGeneratorBuilder:

auto cg_opt = c2pa::C2PAFactory::new_generation_options();
// Modify the options as desired before passing them to the "constructor".
auto builder = c2pa::C2PAFactory::new_claim_generator_builder(cg_opt);

The following sections describe the options that can be configured with this structure.

Specify a log location

The ClaimGenerator will only write to a debug log if this value is set. For now, only file destinations are supported.

cg_opt->log_location = c2pa::C2PAFactory::new_file_asset("c2pa.log");

Set a prefix for custom assertions

By default, custom assertions will use the assertion_label exactly as it's passed in to the add_custom_assertion(). Setting this value to something other than an empty string will prepend a prefix to each custom assertion_label.

cg_opt->custom_assertion_label_prefix = "com.truepic.";

Defining the Claim Generator information

The Claim Generator information is stored in the claim_generator_info structure in the Claim. By default, this will be set to information describing the Truepic libc2pa library. That info can be overridden by setting this property.

cg_opt->claim_generator_info_json = R"(
{
  "name": "My Claim Generator",
  "version": "0.0.0",
  "my_custom_property": "My custom value"
})";

Defining a Claim Generator icon

The Claim Generator icon is also stored in the Claim. There is no default icon. Set this property to specify the icon to use when creating the Claim.

cg_opt->claim_generator_icon = c2pa::C2PAFactory::new_copy_buffer_asset(cg_icon_png_data);

Forcing the selection of a hard binding assertion

By default, the ClaimGenerator will choose the best hard binding assertion for an input file based on specification support, internal support, and industry support. However, this selection can be overridden by setting this property:

cg_opt->force_hash_type = common::HashAssertionType::Data;

Valid values are:

  • common::HashAssertionType::Boxes
  • common::HashAssertionType::Data
  • common::HashAssertionType::Auto

Forcing the generation of an external Manifest Store

The format (specifically the hard binding) of an embedded Manifest differs somewhat from that of an external Manifest. For file types that support an embedded Manifest Store, the ClaimGenerator will always default to creating a Manifest that is appropriate for embedding in the media file. However, there are situations where it might be desirable to create an external Manifest Store even for file types that support embedding.

Unfortunately, due to the way the Manifest is compiled before it is signed, the ClaimGenerator needs to know up-front whether it will be generating an embeddable or external Manifest. This information can be provided by setting this property:

cg_opt->force_external_manifest_store = true;

Setting this property to true is unnecessary for media types that don't currently support embedding a Manifest Store.

When this property is set to true, the client must call the create_manifest_store() method rather than the sign_media() method. Calling the wrong method will throw an exception.

Disabling certificate validation

Before signing a Claim, the ClaimGenerator will always try to perform validation on the provided X509 certificate. If this validation fails, then an exception will be thrown and signing will not proceed. This default behavior can be disabled (only in testing / debugging scenarios) by setting this property:

cg_opt->validate_certificate = false;

Specify a user-defined trust list

The user_trust_list property allows the user to specify a set of trust roots that can be used (in addition to the built-in trust list) to establish trust in signer certificates. It is a list of ReadOnlyAssetPtr objects, and so can contain any mixture of files and data buffers, each containing a certificate in either DER or PEM format. In the case of PEM data, it is acceptable to pass in a certificate chain as a single Asset object.

This trust list will be used by the ClaimGenerator to validate the signing certificate before the Claim is signed.

cv_opt->user_trust_list.push_back(
  c2pa::C2PAFactory::new_read_only_file_asset("my_root_cert.pem"));
cv_opt->user_trust_list.push_back(
  c2pa::C2PAFactory::new_copy_buffer_asset(other_root_cert_der_buffer));
cv_opt->user_trust_list.push_back(
  c2pa::C2PAFactory::new_read_only_file_asset("a_cert_chain.pem"));

Forcing the creation of a c2pa.claim.v2 Claim

There is currently very little industry support for Claims that adhere to the v2.0 version of the C2PA specification. For this reason, the default behavior of the ClaimGenerator is to create a v1.x compliant Claim. This behavior can be overridden with the following property:

cg_opt->enforce_v2 = true;

Chain Calls to the ClaimGeneratorBuilder

The methods on the ClaimGeneratorBuilder support the Fluent design pattern. This means that they return a pointer to the ClaimGeneratorBuilder instance so that the calls can be chained:

#include "C2PA/C2PAFactory.hpp"

void main() {
  // Create a ClaimGeneratorBuilder
  auto builder = c2pa::C2PAFactory::new_claim_generator_builder();
  
  // Call methods on it
  builder
    // Provide claim information
    ->set_media_title("image.jpg")
    ->set_instance_id("3cc0ae0a-96df-4097-8d5f-ebc85dd1d996")
    
    // Provide assertions
    ->add_assertion("metadata", exif_json)
    ->add_assertion("thumbnail", thumbnail_json)
    
    // Provide signature information
    ->set_algorithm(common::SignatureAlgorithm::PS256)
    ->set_certificate_chain(certificate_chain_assets)
    
    // Provide media data
    ->set_media_data(input_jpeg_asset);

  // Build the ClaimGenerator
  auto cg = builder->build();
}

Order of Operations

Calls can be made to the ClaimGeneratorBuilder in any order, but the build() call must be the last call made. Once the ClaimGenerator is built, a timestamp must be added (if desired) before signing the Claim.

In general, this is a good order to follow when signing a claim:

  1. Create a ClaimGeneratorBuilder instance (required)
  2. Provide claim information (optional)
  3. Provide assertions (optional)
  4. Provide signature information (required)
  5. Provide media data (required)
  6. Build the ClaimGenerator (required)
  7. Provide a timestamp (optional)
  8. Perform the signature operation (required)

Provide Claim Information

This information will be placed into the Claim portion of the C2PA Manifest.

set_media_title()

Optional

This is an optional step, but it is recommended that you set this value to something meaningful.

  • std::string media_title - Can be set to any string, but is typically the filename of the input media file.
builder->set_media_title("image.jpg");

set_instance_id()

Optional
  • std::string instance_id - Can be set to any string, but ideally should be a unique identifier, such as a GUID.
builder->set_instance_id("3cc0ae0a-96df-4097-8d5f-ebc85dd1d996");

Provide Assertions

Assertions are statements that the Claim Generator asserts are true about the media file, and the environment in which it was created. Any number of assertions can be added before the file is signed, and each will be placed in the Assertion Store portion of the C2PA Manifest.

As of v2.0 of the C2PA specification, assertions may be of two types:

  • Created - Created assertions refer to a statement that the Claim Signer asserts to be true. Specified with the enumeration value common::CREATED_ASSERTION.
  • Gathered - Gathered assertions refer to information that the Claim Signer has gathered from other sources at Claim generation time. Specified with the enumeration value common::GATHERED_ASSERTION.

If the Claim Generator is creating a v1.x compliant Claim, then there is no distinction, but if a v2.0+ compliant Claim is desired, then the type of assertion should be specified when adding the assertion to the Claim.

add_assertion()

Optional

This adds one of the "standard" assertions supported by libc2pa to the Assertion Store. See Working With Assertions for more info on supported standard assertions. This is an optional step, and may be called any number of times to add multiple assertions.

  • std::string assertion_type - An identifier denoting the type of assertion to add.
  • std::string assertion_json_string - A string containing a JSON structure describing the assertion to add.
  • common::C2PAAssertionSource assertion_source - Optional, Default: GATHERED_ASSERTION Defines wether the created assertion will be listed in the created_assertions or gathered_assertions field of a v2.0+ compliant Claim. This parameter is ignored when generating a v1.x compliant Claim.
builder->add_assertion(
  "exif",
  R"({
    "tiff:Make": "Google",
    "tiff:Model": "Pixel 7"
  })",
  common::GATHERED_ASSERTION
);

add_custom_assertion()

Optional

This adds a "custom" assertion (anything that isn't a supported "standard" assertion) to the Assertion Store. This is an optional step, and may be called any number of times to add multiple assertions.

  • std::string assertion_label - Provides a label for the assertion. When placed in the Assertion Store, the final assertion label will be constructed using the provided value, following the template: <custom_assertion_label_prefix><assertion_label>. (This can be modified by setting the custom_assertion_label_prefix in the GenerationOptions structure.)
  • std::string assertion_json_string - A string containing the exact JSON to be placed in the assertion.
  • bool convert_to_cbor - Optional, Default: true Defines whether the created assertion will be of type CBOR or JSON.
  • common::C2PAAssertionSource assertion_source- Optional, Default: GATHERED_ASSERTION Defines wether the created assertion will be listed in the created_assertions or gathered_assertions field of a v2.0+ compliant Claim. This parameter is ignored when generating a v1.x compliant Claim.
builder->add_custom_assertion(
  "my-cusom-assertion",
  R"({
    "prop1": "val1",
    "prop2": "val2"
  })",
  common::GATHERED_ASSERTION
);

set_provided_thumbnail_assertion()

Optional

When adding a standard thumbnail assertion, the libc2pa code will automatically resize the media and generate the thumbnail image for you. However, in some situations it is preferable to generate the thumbnail with some other external process and provide it to the library to be included in the Manifest.

  • c2pa::ReadOnlyAssetPtr thumbnail_asset - An Asset containing information about how to obtain the raw thumbnail image data (in JPEG, PNG, WebP, or AVIF format) to be stored in the assertion.
  • common::C2PAAssertionSource assertion_source- Optional, Default: GATHERED_ASSERTION Defines wether the created assertion will be listed in the created_assertions or gathered_assertions field of a v2.0+ compliant Claim. This parameter is ignored when generating a v1.x compliant Claim.
builder->set_provided_thumbnail_assertion(jpeg_thumbnail_asset);

Provide Signature Information

set_algorithm()

Required

This informs the ClaimGeneratorBuilder of the cryptographic algorithm that will be used to generate the signature.

  • common::SignatureAlgorithm algorithm - Valid values are:
    • common::SignatureAlgorithm::ES256 - 256-bit elliptical curve
    • common::SignatureAlgorithm::ES384 - 384-bit elliptical curve
    • common::SignatureAlgorithm::ES512 - 512-bit elliptical curve
    • common::SignatureAlgorithm::PS256 - 256-bit RSA-PSS
    • common::SignatureAlgorithm::PS384 - 384-bit RSA-PSS
    • common::SignatureAlgorithm::PS512 - 512-bit RSA-PSS
builder->set_algorithm(common::SignatureAlgorithm::ES256);

set_certificate_chain()

Required

This provides both the end-entity certificate that links the public key associated with the signing (private) key to the trust root.and any intermediate certificates needed to link that end-entity certificate to a trust root in the trust chain. The trust root should not be provided in this call.

  • std::vector<c2pa::ReadOnlyAssetPtr> certificate_chain - An array of raw X509 certificates. It is a list of ReadOnlyAssetPtr objects, and so can contain any mixture of files and data buffers, each containing a certificate in either DER or PEM format. In the case of PEM data, it is acceptable to pass in a certificate chain as a single Asset object. The order of the certificates isn't important. They will be sorted correctly before being placed into the Claim.
builder->set_certificate_chain({
  c2pa::C2PAFactory::new_copy_buffer_asset(end_entity_cert_der),
  c2pa::C2PAFactory::new_read_only_file_asset("intermediate_cert_chain.pem")
});

Provide Media Data

There are two types of media data that can be provided to the ClaimGeneratorBuilder:

  1. Input media data (Required) - This is the media file that is to be signed. It must be one of the supported media types (JPEG, PNG, MP4, etc.)
  2. Ingredient media data (Optional) - This is one or more ingredient files used to compose the input media file.

Ingredients can be signed or unsigned. If there are no signed ingredients provided, then the Manifest Store in the resulting output media file will only contain a single C2PA Manifest. However, if at least one signed ingredient is provided, then the Manifest Store in the resulting output media file will contain a Manifest for each signed ingredient plus a new Active Manifest to represent the input media file. The Active Manifest will reference all ingredient media files with c2pa.ingredient.v2 assertions.

set_media_data()

Required

This provides the input media.

  • c2pa::ReadWriteAssetPtr media - The raw data of the input media file.
builder->set_media_data(c2pa::C2PAFactory::new_ref_buffer_asset(jpeg_buffer));

set_yuv_media_data()

Optional

This is a method added specifically for customers that want to use a YUV workflow.

When working with YUV input, this method should be called instead of set_media_data().

  • c2pa::ReadWriteAssetPtr media_data - The raw YUV data of the input media file, in YUV data in NV12 format (4:2:0 subsampling, u / v are interleaved).
  • int width - The width of the full (Y) image.
  • int height - The height of the full (Y) image.
  • int stride - The number of bytes between scanlines in the full (Y) image.
  • int quality - The desired output JPEG compression level, in the range [1..100].
builder->set_yuv_media_data(
  c2pa::C2PAFactory::new_ref_buffer_asset(yuv_buffer),
  1280, 1024, 1320, 90);

add_ingredient()

Optional

This can be called as many times as desired to add ingredient files to the C2PA Claim.

  • c2pa::ReadOnlyAssetPtr ingredient - The raw data of the ingredient media file.
  • std::string title - Optional For signed ingredients, the title will be pulled from the active Claim and this parameter will be ignored. For unsigned ingredients, this provides an opportunity to specify a title. If none is provided, then a random title will be generated.
buider
  ->add_ingredient(
    c2pa::C2PAFactory::new_ref_buffer_asset(jpeg_ingredient),
    "jpeg_ingredient.jpg")
  
  ->add_ingredient(c2pa::C2PAFactory::new_read_only_file_asset("png_ingredient.png"));

add_ingredient_with_external_manifest_store()

Optional

This can be called as many times as desired to add ingredient files to the C2PA Claim. It allows the media file and an external Manifest Store to be passed in separately.

  • c2pa::ReadOnlyAssetPtr manifest_store - The raw data of the external Manifest Store.
  • c2pa::ReadOnlyAssetPtr ingredient - The raw data of the ingredient media file.
builder->add_ingredient_with_external_manifest_soter(
  c2pa::C2PAFactory::new_ref_buffer_asset(manifest_store),
	c2pa::C2PAFactory::new_read_only_file_asset("ingredient.jpg"));

Build the ClaimGenerator

The ClaimGenerator is created by calling build() on the ClaimGeneratorBuilder. This should be done only one time, after all of the claim attributes have been fully defined via calls to methods on the builder object. After a call to build(), the ClaimGeneratorBuilder instance is invalid and must be discarded.

build()

Required

This returns an instance of ClaimGenerator wrapped in a std::unique_ptr.

auto cg = builder->build();

Provide a Timestamp

A timestamp from a TSA (Time Stamping Authority) may optionally be added to the C2PA Signature. This step (if performed) must be done before the call to get_to_be_signed() The process for doing this is:

  1. Get a TSA request from the ClaimGenerator.
  2. Send (POST) that TSA request to a TSA.
  3. Provide the TSA response (the response to the POST request) to the ClaimGenerator.

The libc2pa library contains helper methods to assist with step 2, if desired. See libc2pa Utility Functions for more info.

get_tsa_request()

Optional

Generates and returns a TSA request, as a std::vector<uint8_t>, to be sent to the TSA.

auto request = cg->get_tsa_request();

set_tsa_response()

Optional

Adds the response from the TSA to the C2PA Signature.

  • std::vector<uint8_t> response - The response data returned from the TSA.
auto response = post_to_tsa(request);
cg->set_tsa_response(response);

Perform the Signature Operation

This is the final step of the signing process. It can only be done once, and after the signature operation is complete, the ClaimGenerator instance is unusable. A new instance must be created to perform another signing operation.

The process for signing the claim is:

  1. Get the TBS structure (to-be-signed structure) from the ClaimGenerator.
  2. Calculate a cryptographic signature over the TBS structure using the private key that matches the public key in the previously provided end-entity certificate.
  3. Provide the resulting signature back to the ClaimGenerator to be included in the C2PA Signature.

The libc2pa library contains helper functions to assist with step 2, if desired. See libc2pa Utility Functions for more info.

The signature of the method for signing the media differs depending on which type of ClaimGenerator was created (buffer, stream, or file).

get_to_be_signed()

Required

Generates and returns a TBS structure, as a std::vector<uint8_t>, to be cryptographically signed.

auto tbs_structure = cg->get_to_be_signed();

sign_media()

Required

Embeds the new C2PA Manifest Store (with all Manifests, Claims, Signatures, etc.) into the input media file. It returns the resulting output media file as a c2pa::ReadWriteAssetPtr. The type of Asset returned will match the type of Asset passed in to the set_media_data() method. If a buffer Asset was provided, then a buffer will be returned. If a std::stream was provided, then that type will be returned. If a file was provided, then a file Asset will be returned. If the output is a buffer Asset, then it will be a newly generated buffer, and the original data buffer will not have been modified. If the output is either a std::stream or a file, then they will point to the original stream or file that was provided to the library, and that asset will have been modified in-place to embed the Manifest Store.

  • std::vector<uint8_t> signature - The signature produced when signing the TBS structure.
auto output_asset = cg->sign_media(signature_buffer);