Skip to content

feat: group file envelope encryption - #891

Open
dastansam wants to merge 4 commits into
mainfrom
feat/group-file-envelope-encryption
Open

feat: group file envelope encryption#891
dastansam wants to merge 4 commits into
mainfrom
feat/group-file-envelope-encryption

Conversation

@dastansam

Copy link
Copy Markdown
Contributor

No description provided.

@dastansam
dastansam marked this pull request as ready for review June 20, 2026 16:38
@dastansam
dastansam force-pushed the feat/group-file-envelope-encryption branch from 0b7ebf4 to 1e84d0d Compare June 20, 2026 17:51
@dastansam dastansam self-assigned this Jun 20, 2026
@dastansam
dastansam requested a review from MathJud June 24, 2026 07:22
Per-file random symmetric key + one ciphertext body for the whole group,
with the file key distributed to each member via the existing per-peer
session (envelope / hybrid encryption). Cuts group file send cost from
O(members × file_size) to O(file_size) + O(members × 32 bytes).

Covers: file-side encryption (ChaCha20-Poly1305 body, BLAKE3 digest
binding), FileKeyEnvelope delivery over CryptoService, receiver
envelope/body matching, membership edges (late joiners via rate-limited
key relay, removed members), protobuf shape, file_keys sled tree,
GroupFiles config, capability negotiation with per-send fallback, risks,
rollback, and a decision gate.

Supersedes the earlier draft on docs/group-file-encryption-fanout
(docs/proposals/Group-File-Encryption-Fanout.md).
First slice of envelope (hybrid) encryption for group files
(docs/proposals/Efficient-Group-File-Encryption.md): encrypt the body
once under a random per-file key and distribute that key per-member,
instead of encrypting the whole file once per recipient.

- Wire: FileKeyEnvelope + FileKeyRelayRequest added to
  CryptoserviceContainer (crypto_net.proto); regenerated. file_id kept
  as uint64 to match the existing file infrastructure.
- Crypto primitives: services/chat/file_envelope.rs — generate_file_key
  (32 random bytes), encrypt_body/decrypt_body (reuse Noise
  ChaCha20-Poly1305 CipherState; fixed nonce is safe since the key is
  fresh per file), body_digest (SHA-256, binds envelope to body). 5 unit
  tests (round-trip, wrong key, tamper, digest binding, key uniqueness).
- Config: GroupFiles section (envelope_enabled default false,
  allow_key_relay, envelope_ttl_seconds=30d,
  max_relay_requests_per_minute), serde(default) + upgrade path.
- sessionmanager: placeholder receive arms for the new variants.

Builds clean, libqaul tests pass.

Follow-ups (next slices): file_keys sled tree; sender path in
services/chat/file.rs (encrypt body once + fan out envelopes); a
sign-only body distribution path so the body isn't re-encrypted per
member; receiver envelope<->body matching + digest verify; key relay
for late joiners. Capability-based per-member downgrade is deferred —
it needs the Capabilities bitset infra that currently lives only on the
crypto-session-rotation branch.
Add a per-account `file_keys` sled tree (on UserFiles) keyed by file_id,
holding `FileKey` entries — received envelope keys awaiting their body
and locally generated keys for sent files that may need relay. Includes
save/get/delete plus TTL-based prune (GroupFiles.envelope_ttl_seconds),
wired through all UserFiles construction sites incl. the graceful
tree-open fallbacks. delete zeroizes via drop. 2 unit tests
(save/get/delete round-trip, prune-only-expired).

Builds clean; libqaul tests pass.
…t working]

Integrates the envelope path end-to-end (structurally): sender encrypts
the body once and distributes it via a new signed-but-not-encrypted
"plain" messaging payload, plus fans out the per-file key to each member.

- messaging: EnvelopPayload::Plain variant (proto) + pack_and_send_plain_data
  + receive routing through on_decrypted_message (signature still verified).
- chatfile: ChatFileInfo.body_digest marks envelope mode + binds the body.
- sender: ChatFile::send_envelope (gated on GroupFiles.envelope_enabled) —
  encrypt body once, store FileKey locally, send FileInfo+body chunks via
  the plain path, fan out FileKeyEnvelope per member over the per-peer
  session; Group::send_to_remote_members_plain.
- receiver: process_file_key_envelope stores the key + retries assembly;
  store_file is envelope-aware (reassemble body_ct, verify SHA-256 digest,
  decrypt with the file key, write plaintext). FileHistory.body_digest
  remembers envelope mode across chunk arrivals.
- CryptoSessionManager::create_file_key_envelope_message + receive arm.

Builds clean; libqaul unit tests pass (incl. the earlier envelope crypto
+ file_keys tests). Fixed an off-by-one (envelope message_count must use
the legacy "actual+1" convention the receiver's completion check expects).

KNOWN-NOT-WORKING (live 2-node test): the encrypted FileKeyEnvelope
reaches the receiver, but the plain body messages do not reach the
receiver's file handler (0 chunks saved), so the file never assembles.
Also: confirmations for plain/crypto control messages fail before a Noise
session exists on first contact ("Can't send further messages after
handshake"). Both need debugging next:
 - why the EnvelopPayload::Plain body isn't dispatched on the receiver
   (encrypted path works; plain doesn't) — likely send/schedule or the
   CommonMessage are_members gate on the direct group.
 - confirmation path should tolerate "no session yet" (queue/retry).
@dastansam
dastansam force-pushed the feat/group-file-envelope-encryption branch from 1e84d0d to 4049180 Compare July 24, 2026 05:41
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant