Ctrl K
ComunidadGuías…

docs/c2pa-x509-vault-setup.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# C2PA X.509 Vault PKI — Operator Runbook

agent-ux-07 M6 ships a Vault-custodied X.509 trust anchor for C2PA Content
Credentials. This document captures the prod Vault setup state, anchor
distribution policy, rotation playbook, the EU AI Act Article 50 mapping,
and the one known limitation.

> **Status (2026-05-28).** Vault prod (`https://100.76.91.69:8200`) has the
> `pki-c2pa/` mount + `c2pa-leaf` role configured and verified. Daemon-side
> signer (`VaultPkiX509Signer`) and CLI (`corecruxctl c2pa-cert-status`,
> `c2pa-rotate-leaf`, `c2pa-verify`) ship in this PR. Public C2PA Viewer
> green-tick is commercial-CA-gated; offline + AI-Act conformance is
> achieved by the self-signed anchor below.

---

## 1. One-shot Vault setup (COMPLETED 2026-05-28)

The commands below were run on the prod cuecrux-vault by the operator on
2026-05-28. **Do not re-run on prod** — they replace the root and would
invalidate every existing leaf and verifier copy of the anchor. Reproduce on
a fresh Vault only.

```bash
# 1. Enable the PKI secrets engine at pki-c2pa/
vault secrets enable -path=pki-c2pa pki

# 2. Configure a 10-year max TTL on the mount
vault secrets tune -max-lease-ttl=87600h pki-c2pa

# 3. Generate the root cert (P-256, ecdsa-with-SHA256). NEVER leaves Vault.
vault write -format=json pki-c2pa/root/generate/internal \
  common_name="CueCrux C2PA Root CA" \
  organization="CueCrux Ltd" \
  country="GB" \
  key_type=ec \
  key_bits=256 \
  ttl=87600h \
  > /tmp/c2pa-root.json

# Save the public cert for distribution (see §3).
jq -r .data.certificate /tmp/c2pa-root.json \
  > ~/.config/cuecrux/c2pa/cuecrux-c2pa-root.pem

# 4. Configure issuer URLs (optional; lets `vault read pki-c2pa/issuer/...`
#    populate the AIA + CRL distribution point extensions).
vault write pki-c2pa/config/urls \
  issuing_certificates="https://100.76.91.69:8200/v1/pki-c2pa/ca" \
  crl_distribution_points="https://100.76.91.69:8200/v1/pki-c2pa/crl"

# 5. Define the c2pa-leaf role with the STRICT C2PA profile:
#    - 30-day TTL on leaves
#    - KU = DigitalSignature only
#    - EKU = EmailProtection only
#    - BasicConstraints CA:FALSE asserted
#    - all auxiliary fields disabled
vault write pki-c2pa/roles/c2pa-leaf \
  ttl=720h \
  max_ttl=720h \
  allow_any_name=true \
  enforce_hostnames=false \
  key_type=ec \
  key_bits=256 \
  use_csr_common_name=true \
  use_csr_sans=false \
  basic_constraints_valid_for_non_ca=true \
  key_usage="DigitalSignature" \
  ext_key_usage="EmailProtection" \
  no_store=false \
  generate_lease=false \
  client_flag=false \
  server_flag=false \
  code_signing_flag=false \
  email_protection_flag=true
```

Verify the root profile out-of-band:

```bash
openssl x509 -in ~/.config/cuecrux/c2pa/cuecrux-c2pa-root.pem \
  -noout -subject -issuer -dates -fingerprint -sha256
# Subject = Issuer = "CueCrux C2PA Root CA" (self-signed)
# SHA256 Fingerprint = 9F:BE:91:9D:E7:75:74:1B:2C:4A:E9:8C:30:A1:11:F1:
#                      4B:96:64:F8:E7:15:6D:FF:30:E1:93:51:C0:67:60:B7
```

---

## 2. Profile verification — `verify-profile.sh`

Run this from any operator workstation with `VAULT_ADDR` + `VAULT_TOKEN` in
the env. It mints a throwaway leaf, asserts the strict KU/EKU/BC profile,
and fails non-zero on drift. Hook it into CI to catch accidental Vault
config edits.

The script lives at [`docs/verify-profile.sh`](./verify-profile.sh) and is
executable.

Expected output on a healthy Vault:

```
PROFILE OK: leaf has KU=DigitalSignature only, EKU=EmailProtection only,
            BasicConstraints CA:FALSE asserted.
```

---

## 3. Anchor distribution

Third-party verifiers need the root certificate to validate any leaf the
daemon emits. The canonical anchor lives at:

```
~/.config/cuecrux/c2pa/cuecrux-c2pa-root.pem   (716 bytes)
```

with SHA-256 fingerprint:

```
9F:BE:91:9D:E7:75:74:1B:2C:4A:E9:8C:30:A1:11:F1:4B:96:64:F8:E7:15:6D:FF:30:E1:93:51:C0:67:60:B7
```

Distribution channels:

| Audience | Channel | Pin |
|---|---|---|
| End-users running `corecruxctl c2pa-verify` | Bundled with the CLI release at `/usr/share/cuecrux/c2pa-root.pem` | SHA-256 fingerprint above |
| Partner integrations | Out-of-band over a TLS-pinned channel (Slack DM, signed email, GPG-encrypted file) | SHA-256 fingerprint above |
| Public CLI users | `https://cuecrux.com/.well-known/c2pa-anchor.pem` (TLS via Let's Encrypt) | SHA-256 fingerprint above |
| Adobe Content Credentials Verify | Not accepted (requires commercial CA) — see §6 |

Whatever channel: **the fingerprint must be displayed alongside the PEM**.
Verifiers MUST refuse to trust a PEM whose fingerprint doesn't match the
published value.

`corecruxctl c2pa-verify` reports overall `ok=true` for an X.509 envelope
only when the envelope signature/content checks pass and the presented chain
cryptographically reaches the exact operator-selected anchor. The offline
private-anchor validator checks current certificate validity, signatures,
BasicConstraints, key usages, the CueCrux C2PA leaf EKU, path length, and
unsupported critical extensions. A missing anchor is untrusted, not a soft
success. Revocation and membership in a public C2PA trust list remain separate
checks and are reported as such.

Vault-backed manifests now use genuine ES256: ECDSA P-256 over SHA-256 of
the canonical body. Earlier development builds mislabeled a BLAKE3-prehashed
signature as `es256`; strict verification intentionally rejects those
non-standard envelopes. Do not relabel or silently grandfather them as ES256.
Before enabling the still-default-off X.509 signer, inventory any retained
development artifacts and classify them explicitly as legacy/non-standard.

---

## 4. Rotation playbook

### 4.a Leaf rotation (automatic, 7-day pre-expiry)

The `VaultPkiX509Signer` rotation watcher checks every 1h. When the active
leaf has <7d remaining (the `ROTATION_THRESHOLD_HOURS` constant), it
mints a fresh CSR, POSTs to Vault, atomically swaps the on-disk PEMs, and
updates the in-memory state. **No operator action required.**

To force an early rotation (e.g. after operator suspects key compromise):

```bash
corecruxctl c2pa-rotate-leaf --json
```

This calls `regenerate_leaf` directly; the new leaf replaces the old one
atomically. Existing C2PA manifests signed by the previous leaf remain
verifiable for as long as third parties retain the old leaf's public key
(embedded in the `x5chain` header of those manifests).

### 4.b Root rotation (manual, OPERATOR-GATED)

If the root is suspected compromised:

1. **Quarantine the daemon** — set `CORECRUXD_FEATURE_C2PA_X509_SIGNER=0`
   on every node so no new X.509 manifests are emitted.
2. **Mint a new root** on Vault (see §1 step 3, but write to a NEW mount
   `pki-c2pa-v2/` — never reuse the same mount).
3. **Issue all-new leaves** by pointing the daemon's
   `CORECRUXD_VAULT_PKI_MOUNT` env to `pki-c2pa-v2`.
4. **Re-distribute the new anchor** via every channel in §3 with the new
   fingerprint clearly marked. Old anchors should be marked "REVOKED — do
   not trust manifests after `<date>`."
5. **Document the incident** with
   `mcp__crux__store_fact(entity="incident:<date>", value={symptom, cause, fix_sha, repro_steps})`.

Existing manifests signed by leaves under the OLD root remain verifiable
only as long as the old anchor is in operator-controlled storage; after
expiry of the last old leaf (max 30 days post-cutover), the old anchor
should be archived and removed from active distribution channels.

---

## 5. EU AI Act Article 50 mapping

Article 50 of the EU AI Act requires providers of generative AI to mark
synthetic output in a machine-readable way. The CueCrux output stack
satisfies this through three layered receipts:

1. **`c2pa.actions` assertion** — every manifest carries `action =
   "c2pa.created"` + `digitalSourceType = "trainedAlgorithmicMedia"`
   (the IPTC value the C2PA v2.3 spec defines for fully AI-generated
   media). This is the AI-Act-visible label.
2. **`x5chain` header (this PR)** — the Vault-custodied X.509 chain
   binds the assertion to a verifiable trust anchor, so verifiers can
   cryptographically attribute the manifest to the CueCrux daemon
   without trusting our word for it.
3. **`cuecrux.crown_receipt` custom assertion** — links the C2PA
   manifest to the daemon's internal CROWN receipt chain (Ed25519,
   unchanged), so internal auditors can re-derive the exact agent
   action that produced the artefact.

The self-signed root is sufficient for AI-Act conformance: the spec only
requires the marking to be "in machine-readable format and detectable as
artificially generated or manipulated" (Art. 50(2)). It does not require
a public CA.

A commercial CA-issued anchor remains a future SKU enhancement (it
unlocks the Adobe Content Credentials Verify green tick); the engineering
scaffolding here is identical, only the root cert provenance changes.

> **Legal banner.** This document is engineering best practice aligned
> with the EU AI Act. It is not a legal opinion. Conformity assessment
> for any specific deployment remains the operator's responsibility.

---

## 6. Known gaps

### 6.a EKU non-critical

Vault PKI's role configuration cannot mark the ExtendedKeyUsage
extension as critical (the `ext_key_usage` field always emits a
non-critical extension). The C2PA spec recommends but does not require
critical EKU; the C2PA reference toolchain (`c2patool`, `c2pa-rs`)
accepts non-critical EKU in practice. The strict-profile assertion test
in `vault_pki_x509_signer.rs` checks the KU/BC bits; EKU criticality is
flagged here as a future Vault-side enhancement (probably via a custom
PKI plugin).

If a future C2PA validator rejects non-critical EKU, the fix path is to
either (a) post-process the Vault-returned cert to flip the critical bit
before stuffing it into the x5chain header, or (b) replace the Vault PKI
mount with a custom CA tool that supports per-extension criticality
config.

### 6.b Adobe Content Credentials Verify green tick

Adobe's hosted Verify (https://contentcredentials.org/verify) only
displays the green "verified" tick for manifests signed by a leaf
chained to a CA in their trust list. Self-signed roots will display the
manifest contents but show a "untrusted issuer" warning.

The fix is to migrate the trust anchor to a commercial CA-issued
intermediate (per the C2PA Hardware-Software Binding spec). This is a
future commercial SKU; the engineering scaffolding (Vault PKI custody,
CSR-sign-only daemon, atomic rotation, offline verifier) is unchanged.

---

## 7. References

- C2PA v2.3 spec: https://c2pa.org/specifications/specifications/2.3/
- RFC 9360 (COSE Receipts / `x5chain` header): https://www.rfc-editor.org/rfc/rfc9360
- Vault PKI secrets engine: https://developer.hashicorp.com/vault/docs/secrets/pki
- EU AI Act Article 50: https://artificialintelligenceact.eu/article/50/
- Crux fact `decision:vault-pki-p256-anchor-path` on
  `execplan:agent-ux-07-verifiable-output-receipts-2026-05-27` —
  architectural decision record.
- Crux fact `vault:pki-c2pa-prod-setup` on the same entity — prod Vault
  state snapshot from 2026-05-28.