Warning
This example app is still under technical review and may contain unknown bugs. You are advised to wait until the review is complete before relying on it as an example.
This repo contains a basic demo (conformance-example-app) of a C2PA signing app.
You sign in, upload a JPEG, and the backend
crops it to a 500×500 square and signs the result with a C2PA manifest built
with the CAI Node library. The manifest records the original upload as its
parentOf ingredient plus the opened, resized, and cropped actions (all as
created assertions), validates ingredients against the official C2PA trust
lists, and gets timestamped by the SSL.com C2PA TSA. The signing key lives in
Google Cloud KMS and never leaves it.
Note
This is a tutorial project, for learning and demos only. It relies on paid services — the Firebase Blaze plan and Google Cloud KMS — and it is not intended for production use. Delete the project when you're done so you don't keep getting billed.
Licensed under the MIT license.
This project uses Firebase (Authorization, Storage, Firestore, Cloud Functions), Node.js, Vue.js, and Tailwind CSS.
Requirements:
- Node 22 or newer
- A Firebase account. Cloud KMS needs billing, so the project has to be on the Blaze plan.
- The
firebase,gcloud, andstepCLIs (brew install firebase-cli google-cloud-sdk step)
Create a project at Firebase Console and upgrade it to Blaze. In
the console, turn on Authentication → Email/Password and click Get
started under Storage. Add a web app under Project settings and paste its
config into web/src/firebaseConfig.js.
Then sign in on the command line and wire everything up:
firebase login
firebase use --alias default YOUR_PROJECT_ID
gcloud auth login
gcloud auth application-default login
gcloud config set project YOUR_PROJECT_ID
gcloud services enable cloudkms.googleapis.com compute.googleapis.com
gcloud firestore databases create --location=nam5The key is created in Cloud KMS, and the certificate comes from a small demo CA you make with the step CLI. (A real conforming product gets its certificate from a CA on the C2PA trust list — the demo CA stands in for that.)
cd scripts
npm install
node createCsr.js "conformance-example-app" "Example Corp."That creates the KMS key, writes csr.pem, and records the key paths in
Firestore. The CN is the product name and O is the organization. Now make a
root CA and sign the CSR — one year, with the C2PA claim signing EKU from
c2pa-cert.tpl:
step certificate create --profile root-ca "conformance-example-app Demo Root" root.crt root.key
step certificate sign --template c2pa-cert.tpl --not-after 8760h csr.pem root.crt root.key > cert.pem
cat cert.pem root.crt > chain.pem
node saveCertificate.js chain.pem
cd ..If step says csr.pem is missing, the createCsr.js run above didn't finish —
fix that first. Otherwise the chain is now in Firestore next to the key
paths — everything the backend needs.
Let the functions runtime use the KMS key. (Functions run as the default compute service account, which appeared when Compute Engine was enabled in step 1.)
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:$(gcloud projects describe YOUR_PROJECT_ID --format='value(projectNumber)')-compute@developer.gserviceaccount.com" \
--role="roles/cloudkms.signer"Then install and deploy everything — the functions, the rules, and the web app on Firebase Hosting:
cd functions && npm install && cd ..
cd web && npm install && cd ..
firebase deployYour app is live at https://YOUR_PROJECT_ID.web.app.
Open it, create an account, and upload a JPEG. You get download links for the original and the cropped, signed copy. Drop the signed one on https://verify.contentauthenticity.org. You'll see the parent ingredient, the opened, resized, and cropped actions, and the SSL.com timestamp. The signer shows as untrusted because your demo CA isn't on the C2PA trust list; that's expected.
For local development, npm run dev inside web/ serves the same app
against your deployed backend.
functions/imageEditor.js— crops to 500×500 with sharpfunctions/assertionManager.js— turns the edits into C2PA actionsfunctions/manifestManager.js— builds the manifest:parentOfingredient, edit intent, created assertions, trust lists, TSAfunctions/kmsSigner.js— signs claim bytes with Cloud KMSscripts/createCsr.js— makes the KMS key and a CSR signed by itscripts/saveCertificate.js— puts the certificate chain in Firestore