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’scrate::crypto::unwrap_org_keyauthenticates it (the DH matches the victim’s secret, the bounduser_idis the victim’s), so the victim adopts the attacker’s key and encrypts future org data under it. - Pubkey-table poisoning → genuine
K_orgleak. Overwrite a victim’s pubkey entry with the attacker’s own pubkey; a legitimate holder ofK_orgthen wraps the REALK_orgagainst that attacker pubkey (it believes it is the victim’s), and the attacker unwraps it with their own secret and the victim’suser_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§
- FsOrg
KeyDirectory - Fs-backed reference — one directory of one org’s wraps + member pubkeys,
the
crate::relay::FsRelayanalogue (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, matchingFsRelay. - InMemory
OrgKey Directory - In-memory reference — tests and in-process coordination (the
crate::relay::InMemoryRelayanalogue). - Member
Public Key - A member’s published X25519 identity public key (the recipient key
crate::crypto::wrap_org_keywraps against), addressed byaccount_id.public_hexis the lowercase hex of the 32-byte X25519 point produced bycrate::crypto::x25519_public; the directory stores it opaquely and does not validate the point (validation happens at wrap/unwrap time). - OrgKey
Directory State - 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§
- OrgKey
Directory Error - Failures publishing to / fetching from an org-key directory.
Traits§
- OrgKey
Directory - The publish/fetch surface for one org’s directory.