# Auditor Verification Guide — Evidence Pack Integrity

This guide lets an auditor (or any third party) verify, without contacting the vendor,
that an evidence pack PDF exported from ZEA AssetOps is byte-identical to what the
platform generated, and that the platform's audit trail has not been tampered with.

## 1. What you receive

Every signed export downloaded from the app saves two files: the PDF and a small JSON
sidecar (`<report>.pdf.sig.json`) built from the `X-Evidence-*` response headers the
platform attaches when serving the PDF:

```json
{
  "signature": "<base64 Ed25519 signature>",
  "sha256": "<hex digest of the PDF bytes>",
  "keyId": "<16-hex-char key identifier>",
  "algorithm": "Ed25519",
  "signedAt": "2026-06-09T12:00:00.000Z"
}
```

The signature is computed as `Ed25519(sha256(pdf bytes))` — the SHA-256 digest of the
exact PDF file is signed, so you can check the hash and the signature independently.

**Unsigned copies.** A deployment without a signing key (staging/demo — production
refuses to start without one) serves no `X-Evidence-*` signature headers. Such a
response carries `X-Evidence-Unsigned: true` and the PDF's first page is stamped
`UNSIGNED — NOT FOR AUDIT USE`. Do not accept a pack carrying that banner as evidence.

## 2. Fetch the public verification key

```
GET https://<deployment-domain>/.well-known/assetops-signing-key
```

Returns the Ed25519 public key (PEM) and its `keyId`. Confirm the `keyId` matches the
sidecar. Pin this key out-of-band on first use (e.g. record it in your engagement file).

## 3. Verify with openssl

```bash
# 1. The PDF's hash must equal the sidecar's sha256 field
shasum -a 256 report.pdf

# 2. Verify the signature over that hash
echo -n "<sha256 hex>" | xxd -r -p > digest.bin
echo "<signature base64>" | base64 -d > sig.bin
curl -s https://<domain>/.well-known/assetops-signing-key | jq -r .publicKeyPem > pub.pem
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in digest.bin -sigfile sig.bin
# -> "Signature Verified Successfully"
```

Any byte change to the PDF changes its SHA-256 and fails verification.

### Or verify online

`POST https://<domain>/api/evidence/verify` with multipart fields `file` (the PDF) and
`signature` (base64 from the sidecar) returns `{ "valid": true | false }`. Offline
verification is authoritative; the endpoint is a convenience.

## 4. Audit-trail integrity (what the platform guarantees internally)

- The `AuditLog` table is append-only at the database level: Postgres triggers reject
  `UPDATE` and `DELETE` (migration `20260523140000_audit_log_chain`).
- Every row carries `prevHash`/`rowHash` forming a per-workspace SHA-256 hash chain;
  re-walking the chain detects any out-of-band modification or deletion.
- Evidence files store a SHA-256 content hash at upload time; the file served later can
  be re-hashed against it.

## 5. What to ask the operator for

1. The evidence pack PDF and its `.sig.json` sidecar.
2. The deployment domain (to fetch the verification key).
3. For chain-of-custody questions: the audit-log extract for the relevant resource —
   each entry includes actor, timestamp, IP, and before/after values.

## Limitations (stated plainly)

- Signing proves the artifact came from the platform unmodified; it does not by itself
  prove the truth of field data (sensor readings, photos). Those carry their own trail:
  ingest timestamps, monotonic sequence numbers, device identity, and the audit chain.
- Verification requires the deployment to have `EVIDENCE_SIGNING_PRIVATE_KEY` configured;
  unsigned packs (no sidecar) predate signing or come from unconfigured environments.
