A WordPress plugin that reads and signs C2PA content credentials, enabling conformant product signatures and CAWG organisational identity signatures.
License: Apache-2.0 OR MIT — pick whichever suits you.
WordPress (PHP plugin) Signing Service (Node.js)
┌────────────────────┐ ┌─────────────────────────┐
│ │ HTTPS │ │
│ Media Library ──►│─────────►│ POST /v1/sign │
│ REST API ──►│ │ POST /v1/read │
│ Settings page │◄─────────│ GET /health │
│ │ signed │ │
└────────────────────┘ binary │ Key Provider │
│ ├── local PEM (dev) │
│ └── AWS KMS (prod) │
└─────────────────────────┘
The WordPress server never holds a private key. All cryptographic operations happen inside the signing service, which you deploy in a separate, hardened environment.
| Type | What it proves | Assertion label |
|---|---|---|
product |
Content passed through a specific software product | (standard C2PA claim) |
cawg_org |
Content is associated with a named organisation | cawg.identity |
both |
Both of the above in a single manifest | both |
- PHP 8.1 or later
- WordPress 6.3 or later
- Node.js 20 or later (or Docker)
- A code-signing certificate and private key issued by a C2PA-trusted CA
cd signing-service
# Copy and fill in the configuration
cp .env.example .env
# Set CONTENTAUTH_API_KEY, SIGNING_CERT_PATH, SIGNING_KEY_PATH
# Create the secrets directory (never commit this)
mkdir -p secrets
cp /path/to/your/signing.crt secrets/
cp /path/to/your/signing.key secrets/
chmod 400 secrets/signing.key
# Start with Docker Compose
docker compose up -d
# Verify it is healthy
curl http://localhost:3000/healthThe signing service listens on port 3000 by default. Put it behind a TLS-terminating reverse proxy (nginx, Caddy, AWS ALB) before exposing it to your WP server.
# From the plugin directory:
composer install --no-devThen upload the plugin directory (or zip it) and activate it through Plugins → Installed Plugins.
Go to Settings → Content Authenticity and enter:
- Service URL — the base URL of your signing service (e.g.
https://sign.example.com) - API Key — must match
CONTENTAUTH_API_KEYin the signing service
For production, define the API key as a constant in wp-config.php instead of saving it to the database:
define( 'CONTENTAUTH_API_KEY', 'your-secret-key-here' );Click Test signing service connection to verify connectivity.
Set KEY_PROVIDER=local and point SIGNING_CERT_PATH / SIGNING_KEY_PATH to your PEM files. The key file is cached in memory after the first read; it is never written anywhere by the service.
Set KEY_PROVIDER=aws-kms. See signing-service/src/key-providers/aws-kms.js for the current status and integration notes.
Full KMS support (where the raw private key never exists in memory) requires an "external signer" API in c2pa-node, which is not yet available. Until then, the recommended approach for AWS users is:
- Store the private key as an encrypted secret in AWS Secrets Manager.
- Fetch and decrypt it at signing-service startup using the instance's IAM role.
- Hold the decrypted key in memory only (never write to disk).
- Rotate the key by redeploying the service.
Your certificate must chain to a CA recognised in the C2PA Trust List. Current options include certificates from DigiCert and other C2PA-participating CAs.
For CAWG organisational signatures, the certificate's Subject should identify your organisation.
When signature_type is cawg_org or both, the plugin adds a cawg.identity assertion to the manifest containing:
- Your organisation name and canonical URL (set in plugin settings)
- An optional W3C Verifiable Credential — if your organisation has been issued one by a CAWG-recognised credential issuer, this allows validators to cryptographically verify the organisational claim
The CAWG identity assertion follows the CAWG Identity Assertion specification.
The plugin registers a REST namespace at contentauth/v1.
Returns the C2PA manifest for an attachment. Append ?force=true to bypass the cache and re-read from the file.
Auth: Public for published attachments; edit_post capability for unpublished.
Signs an attachment. Optionally accepts signature_type in the JSON body to override the site-wide setting.
Auth: edit_post capability for the attachment.
Returns signing service health status.
Auth: manage_options capability (administrators).
composer install
composer lint # PHP_CodeSniffer with WordPress standards
composer test # PHPUnitcd signing-service
npm install
npm run dev # auto-restarts on file changes
npm test- Never expose the signing service directly to the internet. Place it behind a private network or VPN and only allow ingress from your WordPress server.
- Rotate the API key if it is ever compromised. Update it in both
wp-config.phpand the signing service environment. - Restrict file permissions on the signing service key file:
chmod 400 signing.key. - Monitor the signing service logs for unexpected signing requests.
- Rate limiting is enabled by default (60 req/min). Adjust
RATE_LIMIT_PER_MINUTEfor your environment.
See CONTRIBUTING.md.
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE-2.0)
- MIT License (LICENSE-MIT)
at your option.