Skip to content

Latest commit

 

History

History
288 lines (223 loc) · 11.9 KB

File metadata and controls

288 lines (223 loc) · 11.9 KB

Encryption

Proofline stores opaque encrypted chunk bytes. The current v1 preview runtime default is the accepted post-quantum payload envelope implemented in internal/envelope/pq and documented in post-quantum-envelope.md. The older AES-256-GCM chunk envelope remains documented here as an explicit simulator/test compatibility profile only.

This milestone does not add backend decryption. The server still validates SHA-256 over uploaded ciphertext bytes, stores those bytes in the configured blob backend, and emits encrypted ZIP evidence bundles.

Runtime Default

The accepted default profile is:

Scheme: proofline-pq-envelope-v1
Suite: proofline-pq-mlkem768-hkdfsha384-aes256gcm-v1
Payload envelope magic: PLPQENC1
Wrapped-key ciphertext magic: PLPQWK1

For uploads, the server validates the public PQ payload frame header against the authenticated request identity before committing the chunk. It checks the scheme, suite, digest, AEAD identifier, stream ID, media type, chunk index, payload type, and that ciphertext bytes exist. It does not decrypt media, store raw CEKs, store ML-KEM shared secrets, or store recipient decapsulation keys. Missing, downgraded, legacy, or malformed envelope headers fail closed.

Wrapped-key record creation validates the accepted PQ wrapping algorithm, version, public wrapping metadata, and base64url-wrapped-key frame shape without unwrapping the CEK. Bundle manifests identify the PQ scheme, suite, and profile and continue to omit raw keys, wrapped-key ciphertext, private deployment details, stored paths, and object keys.

Naming

The explicit compatibility envelope uses Proofline-named identifiers. Earlier experimental safety-recorder envelope identifiers are not accepted by the current parser except in explicit negative tests that prove fail-closed behavior.

Threat Model

The default PQ envelope protects chunk plaintext from the backend, SQLite, configured blob storage, and evidence bundle readers who do not have the client-held recipient key material plus wrapping records. It does not protect metadata that is already sent to the backend, such as incident ID, stream ID, media type, chunk index, timestamps, byte size, and ciphertext hashes.

The simulator key handling in this repository is for development and test use only. Its default PQ key file stores local ML-KEM decapsulation seed material; the compatibility v1 key file stores a local development CEK. Future production client key storage, sharing, recovery, trusted-contact access, account-owner access, and incident-mode sharing are out of scope for the current implementation and are designed separately in key-custody.md, incident-modes.md, and v1-access-control.md.

Compatibility Scheme v1

Use this only with explicit simulator compatibility flags such as --envelope v1. It is not the v1 preview runtime default.

Field Value
Scheme proofline-chunk-encryption-v1
Algorithm AES-256-GCM
Key size 32 bytes
Nonce size 12 bytes
Tag Included in the AES-GCM ciphertext
Associated data Deterministic UTF-8 metadata string

The Go implementation uses the standard library packages crypto/aes, crypto/cipher, crypto/rand, encoding/base64, encoding/binary, and encoding/json.

Generate a fresh random nonce for every encrypted chunk. Never reuse a nonce with the same key. A single key must not be used for more than 2^32 chunks/messages. By default, the simulator generates a fresh ephemeral key per run unless --key-file is supplied.

Associated Data

The AEAD associated data is an exact UTF-8 string:

ProoflineChunk:v1
incident_id=<incident_id>
stream_id=<stream_id>
media_type=<media_type>
chunk_index=<chunk_index>

There is a trailing newline after the chunk_index line. Example:

ProoflineChunk:v1
incident_id=inc_abc
stream_id=str_def
media_type=audio
chunk_index=1

Encryption and decryption must use identical associated data. IDs and media type must not contain newlines, and chunk_index must be positive. This matches streamed upload semantics; legacy unstreamed chunk_index = 0 chunks cannot use this v1 associated data. Decryption fails when incident ID, stream ID, media type, or chunk index differs from the original metadata.

The v1 associated data does not include capture stream group IDs, variant roles, source timeline identity, supersession state, or encrypted GPS/context bindings. Those fields are future planning work in capture-stream-variants.md and encrypted-location-context.md, and they require explicit protocol and envelope review before they are implemented.

Chunk Envelope

Each uploaded .enc file contains:

magic bytes
uint32 big-endian header length
UTF-8 JSON header
AES-GCM ciphertext including authentication tag

Magic bytes are exactly:

PLCHNK1

The JSON header is non-secret:

{
  "version": 1,
  "scheme": "proofline-chunk-encryption-v1",
  "algorithm": "AES-256-GCM",
  "key_id": "kid_...",
  "nonce_b64": "base64url-no-padding-12-byte-nonce",
  "aad": "ProoflineChunk:v1\nincident_id=inc_...\nstream_id=str_...\nmedia_type=audio\nchunk_index=1\n"
}

The implementation rejects malformed magic, truncated envelopes, oversized headers, unknown versions, unknown algorithms, missing fields, wrong nonce lengths, and associated-data mismatches. Header length is capped at 16 KiB. The header must not contain plaintext, secret keys, or server filesystem paths.

Nonce and key values use URL-safe base64 without padding.

Simulator Key Files

By default, the simulator can load or create a local PQ development key file:

{
  "version": 1,
  "scheme": "proofline-pq-envelope-v1",
  "suite_id": "proofline-pq-mlkem768-hkdfsha384-aes256gcm-v1",
  "recipient_key_id": "pqk1_...",
  "recipient_key_version": 1,
  "recipient_role": "trusted_contact",
  "encapsulation_key_b64u": "base64url-no-padding-1184-byte-public-key",
  "decapsulation_seed_b64u": "base64url-no-padding-64-byte-secret-seed",
  "created_at": "2026-06-10T00:00:00Z"
}

recipient_key_id is non-secret. decapsulation_seed_b64u is secret. Do not upload this file, add it to evidence bundles, commit it to git, paste it into logs, or place it in public documentation examples. The simulator writes key files with 0600 permissions where practical.

With --envelope v1, the simulator can instead load or create the legacy compatibility key file:

{
  "version": 1,
  "scheme": "proofline-chunk-encryption-v1",
  "algorithm": "AES-256-GCM",
  "key_id": "kid_...",
  "key_b64": "base64url-no-padding-32-byte-key"
}

key_id is non-secret. key_b64 is secret.

Simulator Usage

Encryption is enabled by default:

PROOFLINE_SIM_USERNAME=admin \
PROOFLINE_SIM_PASSWORD='replace-with-a-long-local-password' \
go run ./cmd/simclient --chunks 5 --interval 1s --download-bundle

Expected output includes the non-secret recipient key ID, encrypted chunk uploads, bundle download, and local decrypt verification for the same run. The simulator does not print raw keys, plaintext, key-file paths, or token-bearing viewer URLs.

To persist a simulator key locally:

PROOFLINE_SIM_USERNAME=admin \
PROOFLINE_SIM_PASSWORD='replace-with-a-long-local-password' \
go run ./cmd/simclient --chunks 5 --interval 1s --download-bundle --key-file /tmp/proofline-sim.key.json

Run it again with the same path to load the existing key:

PROOFLINE_SIM_USERNAME=admin \
PROOFLINE_SIM_PASSWORD='replace-with-a-long-local-password' \
go run ./cmd/simclient --chunks 2 --interval 1s --download-bundle --key-file /tmp/proofline-sim.key.json

Older local examples may have used /tmp/safety-recorder-sim.key.json; that name is historical and is not part of the current protocol or default simulator artifact layout.

To preserve the old raw fake chunk behavior for development compatibility:

PROOFLINE_SIM_USERNAME=admin \
PROOFLINE_SIM_PASSWORD='replace-with-a-long-local-password' \
go run ./cmd/simclient --encrypt=false

Bundle decrypt verification defaults on when --download-bundle and --encrypt are both enabled. It can be disabled with:

PROOFLINE_SIM_USERNAME=admin \
PROOFLINE_SIM_PASSWORD='replace-with-a-long-local-password' \
go run ./cmd/simclient --download-bundle --verify-bundle-decryption=false

Offline --verify-bundle remains available only for explicit --envelope v1 compatibility bundles because PQ bundle ZIPs intentionally do not include the wrapped-key records needed for standalone decryption.

What The Backend Sees

The backend sees opaque uploaded bytes and client-provided metadata. It stores ciphertext and validates SHA-256 over the ciphertext envelope. It also validates the non-secret PQ payload header and accepted-profile wrapped-key metadata. Private owner-authenticated routes can store grant-bound wrapped-key records as encrypted metadata, but the backend does not parse raw CEKs or media keys, store raw keys in SQLite, upload raw keys, decrypt chunks, or expose public decryption endpoints.

Evidence bundles remain ZIP files containing encrypted .enc chunk files and JSON manifests. Bundle manifests include a non-secret hint that client-side encryption is expected and that the server does not decrypt.

Incident Modes And Encryption

Future incident modes do not change the backend ciphertext-only posture by themselves. Emergency incidents, interaction records, safety checks, and evidence notes may have different capture, sharing, or escalation policies, but uploaded media should still be encrypted before upload and treated as opaque ciphertext by the backend unless an explicit future decryption/key-custody design says otherwise.

Future Work

The intended Apple-side equivalent is CryptoKit or Swift Crypto AES-GCM. This repository does not include iOS or Swift code yet.

Future work includes production client key storage, Keychain integration, trusted-contact key access, key sharing, browser/client-side decryption, account-based access, incident-mode sharing, and any explicitly accepted future playable-export design. The intended production key custody direction is a hybrid trusted-contact model documented in key-custody.md, with future access boundaries in v1-access-control.md, browser decryption constraints in browser-decryption.md, and optional break-glass design in break-glass-key-access.md. The accepted first break-glass implementation boundary is wrapped-key release only; server escrow, backend decryption, raw server-held keys, plaintext export, and emergency-services integration require separate review. Password-derived keys, passphrases, production public-key wrapping, key escrow, backend decryption, and browser decryption are not implemented in this milestone.

Future capture stream variant and supersession work may need new encrypted context bindings or source-timeline metadata. That must not be inferred from the current envelope; it is a separate design tracked in capture-stream-variants.md.

The pure post-quantum envelope profile is the current server/simulator runtime default for v1 preview upload validation and reference flows. Future work still includes production client key storage, trusted-contact account delivery, browser decryption, and cross-repository protocol conformance tests.

The simulator-only contact-wrapped key metadata prototype is implemented separately in contact-wrapped-key-metadata-simulator.md. That prototype can model contact public keys, non-secret key IDs, and wrapped stream CEKs in local development artifacts, but it does not change the current v1 envelope, make the backend store raw keys, or make the backend decrypt media. Server-side wrapped-key records remain encrypted metadata behind authenticated owner routes.