website/content/docs/threat-model.mdx
--- title: 'Threat model' description: 'What Birdbrain''s chain-of-custody controls defend against, what they deliberately do not, and why the default timestamp authority is DigiCert.' --- This document states plainly what Birdbrain's chain-of-custody controls defend against, what they deliberately do **not**, and why the default trusted-timestamp authority is DigiCert. It is the reference a brief or an expert declaration can cite. Scope: the on-disk audit manifest (hash chain + signatures, #115/#117), RFC 3161 trusted timestamping (#120), and Shared Cases, where each member's installation keeps its own manifest (#1508). ## The two independent axes Birdbrain reports integrity and trusted time as **separate, orthogonal axes**. Conflating them is the usual way a custody argument gets muddied; keeping them apart is deliberate. | Axis | Question it answers | Mechanism | | --- | --- | --- | | **Integrity** | Have the captured bytes and the manifest chain survived intact? | SHA-256 content hash + hash-linked, per-entry-signed manifest (#115/#117) | | **Trusted time** | Did this content provably exist no later than time *T*, per a third party? | RFC 3161 token from an external TSA, anchored in the manifest (#120) | A byte-perfect capture is **integrity-verified** whether or not it carries a trusted timestamp. Lacking a timestamp is *not* an integrity failure. It is an empty (or pending) trusted-time axis. The trusted-time axis has three states: `rfc3161` (a TSA token attests the content hash), `pending` (an eligible capture awaiting its token), and `none` (a grandfathered legacy capture that predates timestamping). ## What the manifest signature defends against, and what it does not The per-entry signature over each manifest `entryHash` (#117) raises the bar against **casual tampering**. Copying the case directory elsewhere and editing a capture, a hash, or a chain link with ordinary tools will fail verification. The forged entry no longer carries a signature that validates against the installation's public key, and re-hashing one entry breaks every downstream link. It does **not** defend against a determined operator running Birdbrain's own code as themselves. Where an OS credential store is available (DPAPI on Windows, Keychain on macOS, a Linux Secret Service provider such as gnome-keyring, via Electron `safeStorage`), Birdbrain wraps the signing key at rest with it. Even then, an operator who controls the running process controls the key and can, in principle, mint a fresh internally consistent chain. This is an inherent limit of any locally-held signing key and is stated here so no one over-claims it in a brief. Wrapping at rest is **conditional, not guaranteed.** On an installation with no usable credential store, typically a headless or keyring-less Linux install, Birdbrain does not silently degrade. On first run it refuses to generate a signing key until the operator explicitly acknowledges that the private key will be written to disk as a bare PEM, unprotected. Declining quits the app with no key written. Once acknowledged, Settings → Diagnostics reports the resulting state (`protected` or `plaintext`), so a declaration can state which case applies to the installation that produced the evidence rather than assuming the wrapped one. An unprotected key widens the threat above from "an operator who controls the running process" to "anyone with file access to the machine". **The RFC 3161 token is the independent anchor that constrains this gap.** Because an external TSA issues the timestamp over the content hash, it establishes that the captured bytes existed no later than the stamped time, a fact the operator cannot back-date or forge, regardless of their control over the local machine. Integrity proves the bytes are unchanged; trusted time proves *when* they existed; together they are far harder to dispute than either alone. ## Why DigiCert is the default TSA (decision D6 / #112) The default RFC 3161 endpoint is DigiCert's unauthenticated service `http://timestamp.digicert.com`. The legal-defense rationale: - **Ubiquitous trust anchor.** The DigiCert Trusted Root G4 (and the Assured ID root that cross-signs it) is present out of the box in essentially every OS, browser, and document-verification trust store an opposing expert or a court is likely to use. Verification with stock `openssl ts -verify` succeeds offline against a root the verifier already trusts, with no special configuration and no "trust us" step. (Confirmed: see `tests/fixtures/timestamp/README.md`, where a real DigiCert token returns `Verification: OK` against only the bundled root.) - **No account, no key, works out of the box.** The endpoint is unauthenticated, so timestamping functions on first run with nothing to provision and no credential that could be argued to compromise independence. - **De-facto forensic / Authenticode default.** DigiCert is the timestamp authority behind a large share of Authenticode-signed software and is a routine choice in forensic tooling. "We used the same trusted-timestamp authority that underpins code-signing across the industry" is the strongest *defensible in a brief* story available without a paid relationship. The endpoint is configurable in Settings for jurisdictions or engagements that require a specific TSA. Free/hobbyist authorities (e.g. `freetsa.org`) are a documented **development/test fallback only**. Their roots are not in common trust stores and should not anchor evidence. ## Shared Cases A Shared Case is a Case held by more than one installation; the design sizes it for two to five, and the app does not enforce that size. Each member's installation keeps its own signed manifest chain. A `merge` entry in the receiving member's chain records each sync session that brought in entries from another member's chain, naming the chain states it received. ### What verification shows, and what it does not For a Shared Case, checking an exported evidence package, with the standalone verifier or the `verify.sh` script the package ships, shows three things: - Each member's chain was not edited without that member's installation key. - Every `merge` entry names chain states that exist. - Every `<Member Code>-<n>` citation resolves to one entry. It does not show three others: - Who sat at a member's machine. - That any member's TSA is honest. - That a revoked member deleted its copy of the Case. Revoking stops sync with that member; the member keeps what it already holds. The integrity check in the app's own ledger covers only this installation's chain; it does not establish the first two claims for the other members. ### The key is held per member The limits in the signature section above apply to each member's installation separately. An operator who controls a member's running process controls that member's key. Whether that key is wrapped at rest depends on the credential store of that member's machine, so one member's `protected` key says nothing about another member's. ### Receipt timestamps When trusted timestamping is on, the receiving installation submits the Entry Hash of a `merge` entry in its chain to its own TSA and records the token as a `timestamp` entry with `subject: 'entry'`. The token establishes that the receiver's chain held that `merge`, and so the chain states it names, no later than the stamped time. It does not date any capture and does not change any Exhibit's trusted time. Each member stamps with the TSA configured on its own machine; TSA settings do not sync. Not every `merge` gets its own token. Every five minutes the app looks at the newest `merge` in each Shared Case and stamps it if it has no token yet, while sync runs every minute, so most `merge` entries carry no token of their own. Each Entry Hash covers the entry before it, so a token over a later `merge` also bounds every earlier entry in that chain. A `merge` written before the chain's last `import` entry belongs to the source Case's history and is never stamped. ### One key links a member across groups Each installation has one signing key and uses it in every Shared Case it joins. Every member sees each member's public key: the Owner's approval dialog shows its fingerprint. The installation id and the network node id recorded in each `member-add` entry are also the same in every group, and an evidence package's Certification prints the installation id. Two groups that share a member can therefore match that member by any of the three. A member who wants to stay anonymous should set a pseudonym as their operator name. That name is installation-wide. Birdbrain writes it into every Manifest Entry and into the Certification of every evidence package, from every Case on that installation, not only the Shared Case. A pseudonym hides neither the key, the installation id, nor the network addresses: teammates and the relay see this machine's network addresses during connection setup, as the [network use list](/docs/features#network-use) states. ## Canonical verification path In-app trusted-time display performs **structural** parsing only. It confirms the token's message imprint matches the capture's content hash and shows the TSA identity and stamped time. The **canonical**, court-facing verification is stock `openssl ts -verify` against the TSA chain (decision D5 / #113); Birdbrain never hand-rolls CMS signature cryptography. This keeps the authoritative check in a ubiquitous, independently-audited tool rather than in Birdbrain's own code.