Following C2PA Provenance and Configuring Trust with IPWorks LLM
Requirements:
IPWorks LLM
Introduction
IPWorks LLM can record the assets used to make a new file and carry their C2PA Content Credentials into the result. The C2PASigner component adds those assets as ingredients when it signs a new manifest. The C2PAVerifier component reads the resulting provenance chain and reports separate checks for the content binding, signature, signer trust, and certificate status.
This guide extends Getting Started with C2PA Content Credentials. It signs an edited image made from an already signed photograph, requests an RFC 3161 time-stamp token, and checks the result with a private trust anchor.
Tip: Download the latest version of IPWorks LLM to follow along. Start with a signed source image, an edited image, a PFX signing certificate, and a trust anchor for that certificate.
Step 1: Record a Signed Source
Start with original-signed.jpg, a photograph carrying a valid C2PA manifest, and edited.jpg, an edit made from that photograph. The getting started guide shows how to sign an asset. Provide a PFX signing certificate with its private key and the C2PA claim signing extended key usage. Replace the sample file paths, password, claim generator, and TSA URL with values for your environment.
Call AddIngredient with the signed original before Sign. The parentOf relationship identifies the source from which the edit was derived. Use AddAction to record the opening and editing actions, and set TSAURL to an RFC 3161 Time Stamping Authority:
using nsoftware.IPWorksLLM;
class SignDerivedImage
{
static void Main()
{
C2PASigner signer = new C2PASigner();
signer.InputFile = "edited.jpg";
signer.OutputFile = "edited-signed.jpg";
signer.SigningCert = new Certificate(CertStoreTypes.cstPFXFile,
"signer.pfx", "password", "*");
signer.ClaimGenerator = "MyEditor/1.0";
signer.AddIngredient("original-signed.jpg", "Original photograph", "parentOf");
signer.AddAction("c2pa.opened", "", "MyEditor/1.0");
signer.AddAction("c2pa.edited", "", "MyEditor/1.0");
signer.TSAURL = "https://tsa.example.com";
signer.Sign();
}
}
When the source carries a manifest, AddIngredient copies that manifest into the output's manifest store and records its label on the ingredient. If the source has no manifest, the ingredient still records the source, but the verifiable chain ends there. Use componentOf for an asset placed into a composite or inputTo for an input to a process, when those relationships describe what happened.
Setting TSAURL requests a time-stamp token over the signature. If the configured authority cannot be reached or returns no token, Sign fails rather than writing an asset without the requested token. Use a reachable RFC 3161 endpoint in place of the example URL.
Step 2: Follow the Provenance Chain
For a private PKI, supply its trust anchor through TrustedCerts and set UseC2PATrustList to false before calling Verify. This uses only the anchors your application supplies. Although UseC2PATrustList defaults to true, this build has no bundled C2PA trust-list snapshot.
The following example lists the active manifest's ingredients, finds their source manifests by label, and prints the validation results. It follows one level of provenance from the edited image to its sources:
using System;
using nsoftware.IPWorksLLM;
class InspectDerivedImage
{
static void Main()
{
C2PAVerifier verifier = new C2PAVerifier();
verifier.InputFile = "edited-signed.jpg";
verifier.UseC2PATrustList = false;
verifier.TrustedCerts.Add(new Certificate("private-root.cer"));
verifier.Verify();
C2PAManifest active = verifier.Manifests[verifier.ActiveManifestIndex];
Console.WriteLine("Current signer: " + active.SignerSubject);
for (int i = 0; i < verifier.Ingredients.Count; i++)
{
C2PAIngredient source = verifier.Ingredients[i];
Console.WriteLine(source.Relationship + ": " + source.Title);
if (source.ManifestLabel.Length == 0)
{
Console.WriteLine("No source manifest was embedded.");
continue;
}
bool found = false;
for (int j = 0; j < verifier.Manifests.Count; j++)
{
C2PAManifest manifest = verifier.Manifests[j];
if (manifest.ManifestLabel == source.ManifestLabel)
{
Console.WriteLine("Source signer: " + manifest.SignerSubject);
found = true;
break;
}
}
if (!found)
Console.WriteLine("Source manifest was not found in this file.");
}
foreach (C2PAValidationResult result in verifier.ValidationResults)
Console.WriteLine(result.Severity + " " + result.Code + ": " + result.Explanation);
}
}
Ingredients describes the sources declared by the active manifest. ManifestLabel points to an earlier manifest in Manifests when that source carried Content Credentials. A file may contain multiple manifests; ActiveManifestIndex identifies the one describing its current state.
Step 3: Interpret Trust, Time, and Revocation Results
Read ValidationResults after Verify. A completed call is not a blanket pass: failed validation checks are returned as results. For example, claimSignature.validated reports an intact claim signature, assertion.dataHash.match reports a matching content binding, and signingCredential.untrusted reports that the signing certificate does not chain to an active trust anchor. These checks answer different questions.
The signer can include an RFC 3161 time-stamp token, but this build does not validate the token or populate SigningTime. When a token is present, ValidationResults includes timeStamp.untrusted. Do not treat the token as independent proof of when the asset was signed.
The verifier checks stapled OCSP status when present. This build does not make online OCSP or CRL requests. With no stapled response, it reports signingCredential.ocsp.skipped by default; setting CheckRevocation to true reports signingCredential.ocsp.unknown instead, without an online lookup. Interpret either result according to your application's policy.
For a private PKI, private-root.cer must be an anchor that the signer's certificate chains to. Trusting an anchor does not establish that the recorded edits or source descriptions are factually true. It establishes a basis for trusting who signed the claim, while the signature and content binding checks establish whether the recorded claim and asset remained intact.
Conclusion
The ingredient links the edited image to its signed source. Check the signature, content binding, signer trust, time stamp, and certificate-status results separately when assessing the recorded provenance.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@nsoftware.com.