Metadata-Version: 2.5
Name: qf-verify
Version: 0.3.0
Summary: Offline verifier for Query Fanout tamper-evident AI agent audit evidence bundles and evidence packs
Project-URL: Homepage, https://queryfanout.dev
Project-URL: Documentation, https://queryfanout.dev/docs
Author: Query Fanout
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: ai-agents,audit,audit-log,ed25519,evidence,merkle,rfc3161,tamper-evident,verification
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: Legal Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: System :: Logging
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: cryptography>=42
Provides-Extra: tsa
Requires-Dist: asn1crypto>=1.5; extra == 'tsa'
Description-Content-Type: text/markdown

# qf-verify

Independent, offline verifier for [Query Fanout](https://queryfanout.dev) AI
agent audit evidence bundles (`.json`) and evidence packs (`.zip`).

It is small and dependency-light (only `cryptography`; `asn1crypto` is optional
and needed for RFC 3161 timestamps), so an auditor can read it and run it
without trusting the Query Fanout service that produced the evidence. It makes
no network calls.

## Install

```bash
pip install "qf-verify[tsa]"       # from PyPI, with RFC 3161 timestamp support
pip install ./qf-verify            # or from a checkout of the Query Fanout repo
qf-verify --version
```

Python 3.10 or newer.

## Use

```bash
# 1. Get the publisher's public keys from a source other than the evidence itself
curl -s "https://<host>/audit/keys" > keys.json          # public endpoint, no API key

# 2. Verify an evidence bundle or an evidence pack
curl -H "X-API-Key: $QF_API_KEY" "https://<host>/audit/export?run_id=r1" > bundle.json
qf-verify bundle.json --trusted-keys-file keys.json

curl -H "X-API-Key: $QF_API_KEY" "https://<host>/audit/evidence-pack?run_id=r1" -o pack.zip
qf-verify pack.zip --trusted-keys-file keys.json

# 3. Also check who issued the RFC 3161 timestamps: pass the timestamp
#    authority's CA certificate, downloaded from the authority itself
curl -s https://freetsa.org/files/cacert.pem > freetsa-ca.pem
qf-verify pack.zip --trusted-keys-file keys.json --tsa-ca freetsa-ca.pem
```

Exit codes: `0` verified, `1` verification failed, `2` unreadable input.
Add `--json` for machine-readable output (it includes a `timestamps` list).

| Option | Meaning |
|---|---|
| `--trusted-keys-file FILE` | Ed25519 keys from `/audit/keys` (or `query-fanout-audit-admin keys`) |
| `--trusted-key [ID=]BASE64` | pin a single Ed25519 public key (repeatable) |
| `--tsa-ca FILE` | trusted TSA CA certificate(s), PEM bundle or DER (repeatable) |
| `--pin-tsa-cert SHA256` | accept only a TSA signing certificate with this fingerprint (repeatable) |
| `--no-anchors` | skip timestamp checks |
| `--allow-unsigned` | downgrade missing signatures to warnings |
| `--json`, `-q` | machine-readable / quiet output |

## What it checks

See `docs/audit-schema.md` in the Query Fanout repository for the format.

| Check | Catches |
|---|---|
| Record hash (SHA-256 over RFC 8785 canonical header) | edited metadata |
| Payload hash | edited query/sources/decision |
| Ed25519 record + checkpoint + bundle signatures | forged or re-hashed records |
| Merkle inclusion proof vs signed tree head | records not in the log, inserted records |
| Consistency proofs between checkpoints | history rewritten after a checkpoint |
| Manifest + hash-chain links | deleted, inserted or reordered records |
| Retention tombstones | payloads removed without a signed retention event |
| RFC 3161 timestamp tokens on the checkpoint and earlier checkpoints | backdated checkpoints; reported as "records 1–N existed no later than T" |
| `--tsa-ca`: TSA certificate chain (at the timestamp's time), timeStamping EKU, ESS signing-certificate binding | timestamps from an authority you don't trust |
| Pack: signed manifest with per-file SHA-256 | files edited, added, removed or swapped inside the zip |
| Pack: cross-checks | `records.jsonl` / `checkpoint.json` / `anchors.json` / `public-keys.json` disagreeing with the signed bundle |

Packs are read in memory and never extracted. Names containing paths,
duplicate entries, and oversized archives are rejected.

Pin keys (`--trusted-key` / `--trusted-keys-file`) obtained out of band; without
pinning, the verifier trusts the keys embedded in the bundle and says so. In the
same way, without `--tsa-ca` or `--pin-tsa-cert` a timestamp's signature is
checked but the identity of the authority is only reported, with a warning.

Limits: certificate revocation (CRL/OCSP) is not checked, because the verifier
works offline. Chains are evaluated at the timestamp's generation time.

## Changelog

* **0.3.0**: `--tsa-ca` chain validation, timestamps on earlier checkpoints,
  `anchors.json` in packs, `--version`, keys file may be a plain list, PyPI metadata.
* **0.2.0**: evidence packs (`.zip`).
* **0.1.0**: evidence bundles.

## License

Apache License 2.0, see `LICENSE`.
