Badge Signer

Badge signer service: C2PA signing, certificates, c2patool, and Trusteddit PKI integration

Overview

The badge signer is a Python service that generates verifieddit badges with embedded C2PA Content Credentials. It uses c2patool for signing, Pillow for image generation, and qrcode for QR code embedding.

Repository: Sanmarcsoft/verifieddit-www (path: services/badge-signer/)

Components

  • server.py – HTTP server handling badge signing requests
  • badge_png_generator.py – Generates badge PNG images with QR codes
  • badge-manifest-template.json – C2PA manifest template for badge signing
  • c2patool – CAI c2patool binary (v0.9.12, fetched in Nix build)

Certificates

The badge signer requires PKI certificates for C2PA signing:

FileContainer PathDescription
Signing key/certs/signer-key.pemPrivate key for C2PA signing
Certificate chain/certs/chain.pemFull certificate chain (signer + intermediate + root)

Certificate Source

Certificates are managed by the Trusteddit PKI system:

  • Repository: Sanmarcsoft/trusteddit-pki
  • PKI Services: Sanmarcsoft/trusteddit-pki-services
  • Certificate chain follows C2PA 2.2 trust model
  • Root CA is the SanMarcSoft/Trusteddit root

Certificate Rotation

  1. Generate new certificates via the Trusteddit PKI
  2. Update the certificate files in the deployment
  3. Re-deploy the badge signer
  4. Verify signing works with the new certificates
1
2
3
4
5
6
7
# Test signing with new certs
c2patool sign \
  --manifest badge-manifest-template.json \
  --key /certs/signer-key.pem \
  --cert /certs/chain.pem \
  test-image.png \
  -o signed-test.png

Nix Build

The Nix flake:

  • Targets x86_64-linux
  • Uses python312.withPackages for Python dependencies (pillow, qrcode)
  • Fetches c2patool binary from GitHub releases (with autoPatchelfHook for dynamic linking)
  • Includes DejaVu fonts for badge text rendering
1
2
cd services/badge-signer
nix build .#packages.x86_64-linux.oci-image

c2patool Binary

The c2patool binary is fetched as a Nix derivation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
c2patool = pkgs.stdenv.mkDerivation {
  pname = "c2patool";
  version = "0.9.12";
  src = pkgs.fetchurl {
    url = "https://github.com/contentauth/c2patool/releases/download/v0.9.12/c2patool-v0.9.12-x86_64-unknown-linux-gnu.tar.gz";
    sha256 = "sha256-yqM2dH1TZOIeDrejB8EItmgLI3/4uKpe2Uq45D1kIUw=";
  };
  nativeBuildInputs = [ pkgs.autoPatchelfHook ];
  buildInputs = [ pkgs.stdenv.cc.cc.lib pkgs.openssl ];
};

autoPatchelfHook automatically patches the ELF binary to use Nix store paths for shared libraries (libssl, libstdc++).

Environment Variables

VariableContainer ValueDescription
C2PATOOL${c2patool}/bin/c2patoolPath to c2patool binary (Nix store path)
MANIFEST${appFiles}/app/badge-manifest-template.jsonPath to manifest template
PRIVATE_KEY/certs/signer-key.pemPath to signing private key
SIGN_CERT/certs/chain.pemPath to certificate chain
PYTHONUNBUFFERED1Disable Python output buffering

Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
cd services/badge-signer

# Build
nix build .#packages.x86_64-linux.oci-image

# Push
skopeo copy docker-archive:./result \
  "docker://rg.fr-par.scw.cloud/sanmarcsoft/verifieddit-badge-signer:testing" \
  --dest-creds "nologin:$(pass sanmarcsoft/scaleway/api-secret)"

# Deploy
cd infra && pulumi up

Three Pillars of Provenance

The badge signer implements Pillar 1: Hard Binding (C2PA JUMBF) of the Three Pillars of Provenance architecture:

  1. Pillar 1 (implemented): Hard Binding – C2PA JUMBF embedded in image
  2. Pillar 2 (planned): Soft Binding – TrustMark invisible watermark
  3. Pillar 3 (planned): Perceptual Fingerprinting – pHash + dHash

The Manifest Store service (trusteddit-pki-services/manifest-store/) provides the API for Pillars 2 and 3.

Troubleshooting

  • c2patool “not found”: The binary path is set via the C2PATOOL environment variable to the Nix store path. If missing, the entrypoint script or container config is wrong.
  • Certificate errors: Check that /certs/signer-key.pem and /certs/chain.pem are mounted/available. Verify certificate validity with openssl x509 -in /certs/chain.pem -text -noout.
  • Font rendering issues: DejaVu fonts must be included in the image. The Nix flake includes pkgs.dejavu_fonts.
  • Image too large for signing: Very large images may exhaust memory. Check container memory limits.