Skip to main content

Module org_key_directory

Module org_key_directory 

Source
Expand description

Org-key directory — the publish/fetch surface for the client-side org-key agreement (the follow-up named in crypto.rs alongside the merged wrap_org_key/unwrap_org_key primitives).

§What this carries — and why it is NOT the oplog

The org master key K_org is shared across an org’s members by wrapping it once per member (crate::crypto::wrap_org_key, ECIES over X25519) so every member can recover the same key while the relay only ever sees ciphertext. To distribute those wraps, members need a place to publish their WrappedOrgKey blobs and their X25519 public keys, and to fetch the ones addressed to them.

That surface is a key-value directory, not an append-only op stream:

  • a wrapped blob is keyed by (epoch, recipient_user_id) within one org;
  • a member public key is keyed by account_id;
  • there is no sequence chain, no frontier, no GC horizon, no checkpoint dominance — the invariants that define crate::relay::Relay.

So it is deliberately a separate trait, not four more methods bolted onto Relay/SyncTransport. Bolting KV semantics onto the oplog state machine would let a blob be counted in a device frontier or swept by a GC pass that mistook it for a stale op. Routing org-key traffic around the oplog keeps both models honest. (This mirrors the crate’s own idiom: a OrgKeyDirectory trait over a pure OrgKeyDirectoryState machine, driven by both an in-memory and an fs-backed reference — exactly as Relay is driven by InMemoryRelay + FsRelay.)

§Scope, and what this slice is (and is not)

Like crate::relay::Relay, the trait here is single-directory: one handle serves one org’s directory (an FsOrgKeyDirectory is bound to one dir, just as an FsRelay is bound to one scope dir). The scope-keyed, network-faithful form (the org:<id> param, mirroring crate::net_relay::SyncTransport) and the Parslee/m365 backend come in a later slice — this one is the pure reference: no prod caller dispatches it, no SyncTransport/car-parslee change is forced, and it stays inert until the org-scope path is wired behind the cryptographer-audit gate.

§Trust boundary — publisher authz is CONFIDENTIALITY-critical, not availability

This reference is a dumb store: publish_* is last-write-wins and does NOT authenticate the publisher. That is the right shape for a pure state machine — but do not mistake it for “just a DoS surface.” The authz the backend slice must add (“only the account itself may publish its own pubkey, and only a legitimate K_org holder may publish a wrap”) is a confidentiality control. Unauthenticated publish enables two attacks, both strictly worse than denial of service:

  • Wrap-table poisoning → key substitution. A member’s X25519 public key is public (it is served from THIS directory). Anyone can therefore wrap an attacker-chosen K_org' against a victim’s real pubkey and publish it; the victim’s crate::crypto::unwrap_org_key authenticates it (the DH matches the victim’s secret, the bound user_id is the victim’s), so the victim adopts the attacker’s key and encrypts future org data under it.
  • Pubkey-table poisoning → genuine K_org leak. Overwrite a victim’s pubkey entry with the attacker’s own pubkey; a legitimate holder of K_org then wraps the REAL K_org against that attacker pubkey (it believes it is the victim’s), and the attacker unwraps it with their own secret and the victim’s user_id — recovering the real org key.

Neither attack is stopped by the AEAD / contributory-DH checks in unwrap_org_key: those only prove “some wrapper used my user_id and a pubkey matching my secret,” and both inputs are public. The recipient field is likewise ADVISORY (see crate::crypto::WrappedOrgKey) and must not be routed or authorized on. So the backend MUST authenticate the publisher of every publish_wrapped / publish_pubkey; treating that as optional hardening is a key-compromise bug, not a UX one. The one thing the wrap ciphertext itself never leaks is K_org to a passive relay — but the pubkey-poisoning path above leaks it to an active publisher, which is why publish authz cannot be deferred as availability-only.

Structs§

FsOrgKeyDirectory
Fs-backed reference — one directory of one org’s wraps + member pubkeys, the crate::relay::FsRelay analogue (the realistic single-host / shared dir). Every mutation runs under an exclusive lock file and persists the whole state via temp-write + fsync + atomic rename, matching FsRelay.
InMemoryOrgKeyDirectory
In-memory reference — tests and in-process coordination (the crate::relay::InMemoryRelay analogue).
MemberPublicKey
A member’s published X25519 identity public key (the recipient key crate::crypto::wrap_org_key wraps against), addressed by account_id. public_hex is the lowercase hex of the 32-byte X25519 point produced by crate::crypto::x25519_public; the directory stores it opaquely and does not validate the point (validation happens at wrap/unwrap time).
OrgKeyDirectoryState
The pure directory state — the same “trait over a pure state machine” shape as crate::relay::RelayState. Both references drive this; it holds no I/O and is deterministic.

Enums§

OrgKeyDirectoryError
Failures publishing to / fetching from an org-key directory.

Traits§

OrgKeyDirectory
The publish/fetch surface for one org’s directory.