Adding and signing a manifest
- Rust
- C++
- Python
- Node.js
This is an example of how to assign a manifest to an asset and sign the claim using Rust.
Configure a Context with signer settings and use Builder::from_context to create and sign a manifest:
use c2pa::{Context, Builder, Result};
use serde_json::json;
use std::io::Cursor;
fn main() -> Result<()> {
let settings = json!({
"signer": {
"local": {
"alg": "ps256",
"sign_cert": "path/to/cert.pem",
"private_key": "path/to/key.pem",
"tsa_url": "http://timestamp.digicert.com"
}
},
"builder": {
"claim_generator_info": { "name": "My App", "version": "1.0" },
"intent": { "Create": "digitalCapture" }
}
});
let context = Context::new()
.with_settings(settings)?;
let mut builder = Builder::from_context(context)
.with_definition(json!({"title": "My Image"}))?;
let mut source = std::fs::File::open("source.jpg")?;
let mut dest = Cursor::new(Vec::new());
builder.save_to_stream("image/jpeg", &mut source, &mut dest)?;
Ok(())
}
This is an example of how to assign a manifest to an asset and sign the claim using C++:
#include <iostream>
#include <memory>
#include <string>
#include "c2pa.hpp"
using namespace c2pa;
const std::string manifest_json = R"({
"claim_generator_info": [
{
"name": "c2pa-cpp test",
"version": "0.1"
}
],
"assertions": [
{
"label": "c2pa.training-mining",
"data": {
"entries": {
"c2pa.ai_generative_training": { "use": "notAllowed" },
"c2pa.ai_inference": { "use": "notAllowed" },
"c2pa.ai_training": { "use": "notAllowed" },
"c2pa.data_mining": { "use": "notAllowed" }
}
}
}
]
})";
auto context = std::make_shared<c2pa::Context>();
auto builder = c2pa::Builder(context, manifest_json);
Signer signer = c2pa::Signer("es256", certs, private_key, "http://timestamp.digicert.com");
auto manifest_data = builder.sign("source_asset.jpg", "output_asset.jpg", signer);
This is an example of how to assign a manifest to an asset and sign the claim using Python.
Use a Builder object to add a manifest to an asset.
import json
from c2pa import Builder, Context, Signer, C2paSignerInfo, C2paSigningAlg
manifest_json = json.dumps({
"claim_generator": "python_test/0.1",
"assertions": []
})
try:
with open("tests/fixtures/ps256.pub", "rb") as cert_file, \
open("tests/fixtures/ps256.pem", "rb") as key_file:
cert_data = cert_file.read()
key_data = key_file.read()
signer_info = C2paSignerInfo(
alg=C2paSigningAlg.PS256,
sign_cert=cert_data,
private_key=key_data,
ta_url=b"http://timestamp.digicert.com"
)
with Context() as ctx:
with Signer.from_info(signer_info) as signer:
with Builder(manifest_json, ctx) as builder:
# Add an ingredient from a stream.
ingredient_json = json.dumps({
"title": "A.jpg",
"relationship": "parentOf"
})
with open("tests/fixtures/A.jpg", "rb") as ingredient_file:
builder.add_ingredient(ingredient_json, "image/jpeg", ingredient_file)
# Sign and write to the output file.
with open("tests/fixtures/A.jpg", "rb") as source, \
open("target/out.jpg", "w+b") as dest:
builder.sign(signer, "image/jpeg", source, dest)
except Exception as err:
print(err)
Use the Builder and LocalSigner classes from @contentauth/c2pa-node to assemble manifest data and sign an asset.
Create a builder
import { Builder } from '@contentauth/c2pa-node';
// Default settings
const builder = Builder.new();
// With settings (JSON object or string; see [Settings](../settings.mdx))
const withSettings = Builder.new({
builder: { generate_c2pa_archive: true },
});
// From an existing manifest definition (see manifest JSON reference)
const fromDefinition = Builder.withJson({
claim_generator_info: [{ name: 'my-app', version: '1.0.0' }],
title: 'My image',
format: 'image/jpeg',
assertions: [],
resources: { resources: {} },
});
Add assertions and resources
builder.addAssertion('c2pa.actions', {
actions: [{ action: 'c2pa.created' }],
});
await builder.addResource('resource://example/thumb', {
buffer: thumbnailBytes,
mimeType: 'image/jpeg',
});
Sign with a local certificate and key
LocalSigner.newSigner takes the signing certificate (PEM), private key (PEM), algorithm (es256, ps256, ed25519, etc.), and an optional RFC 3161 timestamp URL.
import { Builder, LocalSigner } from '@contentauth/c2pa-node';
import { readFile } from 'node:fs/promises';
const cert = await readFile('signer.pem');
const key = await readFile('signer.key');
const signer = LocalSigner.newSigner(cert, key, 'es256');
const builder = Builder.withJson({
claim_generator_info: [{ name: 'my-app', version: '1.0.0' }],
title: 'output.jpg',
format: 'image/jpeg',
assertions: [],
resources: { resources: {} },
});
builder.setIntent('edit');
// Output to a file
builder.sign(signer, { path: 'input.jpg' }, { path: 'signed.jpg' });
Sign to an in-memory buffer
Use a destination object with buffer: null; after sign, the signed asset bytes are written into dest.buffer.
const dest: { buffer: Buffer | null } = { buffer: null };
builder.sign(signer, { path: 'input.jpg' }, dest);
const signedBytes = dest.buffer;
Callback signing (signAsync)
For signing in hardware, a remote service, or other custom flows, use CallbackSigner and signAsync:
import { Builder, CallbackSigner } from '@contentauth/c2pa-node';
import { readFile } from 'node:fs/promises';
const cert = await readFile('signer.pem');
const callbackSigner = CallbackSigner.newSigner(
{
alg: 'es256',
certs: [cert],
reserveSize: 1024,
},
async (data: Buffer) => {
return customSign(data);
},
);
const builder = Builder.new();
await builder.signAsync(
callbackSigner,
{ path: 'input.jpg' },
{ path: 'signed.jpg' },
);
Replace customSign with your implementation that returns the detached signature bytes for the C2PA claim.
For identity assertions (CAWG), see IdentityAssertionBuilder and IdentityAssertionSigner in the c2pa-node-v2 README.