ProofSnap: Evidence Verification Guide
========================================================================
Evidence ID: ps_0e6c1a29-3e47-4cc7-80ce-d670db22e027

This package is self-contained. Everything needed to verify it is inside the
ZIP. Three ways to verify:

  A) Run the bundled script (from the package root)
       macOS / Linux:  bash verification/verify.sh
       Windows:        powershell -ExecutionPolicy Bypass -File verification\verify.ps1

  B) Verify online, drag the ZIP onto https://getproofsnap.com/verify/index.html

  C) Run the four steps below by hand.

------------------------------------------------------------------------
STEP 1: File integrity (SHA-256)
------------------------------------------------------------------------
manifest.json lists the SHA-256 hash of every file it covers. Recompute each
hash and compare: any mismatch, or any file listed and absent, means the package
was altered after signing. manifest.sig, publickey.pem, manifest.json.tsr,
manifest.json.ots and eidas_validation.json are produced from the finished
manifest and therefore sit outside it by construction; they are checked in
steps 2, 3 and 4 instead.

  macOS/Linux:  shasum -a 256 <file>
  Windows:      Get-FileHash <file> -Algorithm SHA256

------------------------------------------------------------------------
STEP 2: Manifest signature (RSA + SHA-256)
------------------------------------------------------------------------
manifest.sig is a detached RSA signature over manifest.json, made with the
private key whose public half is publickey.pem. Verify:

  openssl dgst -sha256 -verify publickey.pem -signature manifest.sig manifest.json

"Verified OK" proves manifest.json has not been altered since it was signed,
and therefore, via Step 1, that every file it lists is unaltered too. The key
is in this package, so this shows internal coherence, not authorship; what ties
the manifest to a moment in time is the qualified timestamp in Step 3.

------------------------------------------------------------------------
STEP 3: eIDAS RFC 3161 qualified timestamp  (if present)
------------------------------------------------------------------------
manifest.json.tsr is a qualified timestamp token bound to manifest.json,
issued by a Qualified Trust Service Provider on the EU Trusted List. It proves
manifest.json existed at the stamped time. Deep verify with the TSA root cert:

  openssl ts -verify -in manifest.json.tsr -data "manifest.json" -CAfile <tsa-root>.pem

The certificate chain and OCSP/CRL revocation data are in eidas_validation.json
(the "ocspStatus" field carries the status of the TSA certificate at stamping
time; the file records the material for the check, not a verdict). The TSA root
certificate is the "rootCaPem" field of that file: copy its value into a plain
.pem file and pass that file as -CAfile. The root offered there is not covered by the
manifest, so check its SHA-256 fingerprint against the one the authority
publishes before you trust it. For CA Disig QCA4 that fingerprint is
8E:BC:96:10:6E:FE:F2:7F:D3:69:D5:D8:5E:73:6E:4C:4E:31:DD:AC:22:E3:16:89:23:55:C5:3B:28:2E:C1:37.

You need OpenSSL 3.x for this step. LibreSSL, which macOS installs under the
name "openssl", cannot read the ESSCertIDv2 (SHA-256) attribute that qualified
TSAs place in the token: it stops with "ess signing certificate error" and
prints "Verification: FAILED" even when the timestamp is perfectly valid. Such
a failure is a property of that tool, not of this package. Check which one you
have with:

  openssl version

If the answer names LibreSSL, install OpenSSL 3.x (macOS: brew install
openssl@3, then call /opt/homebrew/opt/openssl@3/bin/openssl, or
/usr/local/opt/openssl@3/bin/openssl on Intel Macs) and repeat the command.
Most current Linux distributions already ship OpenSSL 3.x.

If you would rather install nothing, the same signature and the same chain can
be checked with the LibreSSL already present, on the token extracted from the
.tsr. Run it from a directory outside this package, so that nothing is written
into the sealed evidence, and set PKG to this package first:

  export PKG=/path/to/this/package
  openssl ts -reply -in "$PKG/manifest.json.tsr" -token_out -out token.der
  python3 -c 'import json,os;print(json.load(open(os.environ["PKG"]+"/eidas_validation.json"))["rootCaPem"])' > root.pem
  openssl smime -verify -in token.der -inform DER -CAfile root.pem -purpose any -out /dev/null

It must print "Verification successful". Use smime and not cms: the cms
subcommand is absent from the LibreSSL that older macOS releases ship (2.8.3),
which exits with "'cms' is an invalid command", while smime is present in every
version and takes the same arguments.

Independently of the command line, eidas_validation.json holds the material
gathered at stamping time: tsaCertChain, rootCaPem, ocspStatus with the response
it came from, the CRL, and stampedAt, which is the moment the stamp was
requested. The authoritative time is the one inside manifest.json.tsr.

------------------------------------------------------------------------
STEP 4: Bitcoin OpenTimestamps anchor  (if present)
------------------------------------------------------------------------
manifest.json.ots anchors manifest.json into the Bitcoin blockchain. Verify
with the OpenTimestamps client:

  ots verify manifest.json.ots -f "manifest.json"

------------------------------------------------------------------------
Hash chain
------------------------------------------------------------------------
forensic_log.json records the acquisition as a tamper-evident SHA-256 hash
chain, in one of two shapes. The bundled scripts do NOT recompute it: they
check that forensic_log.json is the file the signed manifest lists, which is
what establishes that the log has not been altered. Recomputing the chain is
set out below for a reader who wants to walk it by hand.

Under "operations": each entry's hash = SHA-256 of its compact JSON; the
running cumulative hash = SHA-256(previous_cumulative + entry_hash), seeded with
genesis_hash. The operations are stored in chain order, so recomputing the
cumulative hash over them in order must reproduce hash_chain.final_hash, which
in turn must equal chain_of_custody.json -> forensic_log_final_chain_hash.
forensic_log_summary.txt (package root) is a human-readable rendering of the
same entries.

Scope of the log: it is closed before manifest.json hashes it, because the
timestamps are taken over manifest.json and the signature is made over the
manifest. Whatever happens after that point cannot appear inside the log, and
is evidenced instead by manifest.sig, manifest.json.ots and manifest.json.tsr,
plus the corresponding status fields in chain_of_custody.json.

------------------------------------------------------------------------
C2PA provenance
------------------------------------------------------------------------
screenshot.jpeg carries an embedded C2PA manifest (exported as c2pa.json).
Re-validate at https://contentcredentials.org/verify or with c2patool. Note:
a "signing certificate untrusted" result is expected until ProofSnap's C2PA
certificate is added to the CAI/Adobe trust list; it does not affect the RSA
signature, timestamps, or hash chain above.
========================================================================
