// Package e2ee is a backend-agnostic end-to-end encryption abstraction for // telegabber's XMPP side. It knows nothing about stanza XML shape - a // Backend deals only in opaque byte envelopes; wrapping/unwrapping those // into /PEP XML is the caller's job (see e2ee/omemo for the // first, and so far only, implementation, and its envelope.go for the // XEP-0384 wire format specifically). package e2ee // PeerID identifies one bridging endpoint's cryptographic identity scope. // For telegabber this is always a bare XMPP JID: either a bridged chat's // pseudo-JID (whose keys the gateway owns and generates) or a real remote // user's bare JID (whose keys the gateway only ever fetches). type PeerID string // DeviceID is an opaque per-backend device identifier, serialized as its // natural string form (OMEMO: decimal uint32; a hypothetical single-key // backend could always use "0" or the empty string). type DeviceID string // Envelope is an opaque, backend-defined encrypted payload, plus enough // metadata for the xmpp layer to build the wire XML generically. type Envelope struct { Backend string // e.g. "omemo" - which backend produced Raw, so the xmpp layer picks the right XML shape Raw []byte // backend-specific serialized envelope } // Namespaces is what a Backend wants advertised/served. type Namespaces struct { Disco []string // disco#info features to advertise, across every supported Variant (e.g. "...+notify" hints) } // Variant identifies one of a Backend's parallel wire-format generations // (e.g. OMEMO's omemo0/omemo1/omemo2 - three simultaneously-supported // namespaces sharing one identity/prekey set). It's an opaque string a // Backend defines and interprets itself; other packages only ever pass a // Variant back between Variants()/DefaultVariant()/NegotiatedVariant() and // DeviceListNode()/BundleNode()/PublishedIdentity()/PublishedBundle() - // never construct or compare one directly. A backend with only one // generation can just always return/accept a single fixed value. type Variant string // DeviceListDoc is an opaque, backend-defined serialization of a peer's // PEP device-list document (fetched from them, or published for a chat // pseudo-JID we own). type DeviceListDoc struct{ Raw []byte } // BundleDoc is an opaque, backend-defined serialization of one device's // PEP bundle document. type BundleDoc struct{ Raw []byte } // DeviceInfo describes one known device of a peer, for trust-listing // ad-hoc commands. Trusted is false only if the device has been manually // blocked (see Backend.BlockDevice) - TOFU itself always trusts a // never-before-seen device automatically, so a session existing at all // already implies TOFU accepted it; Trusted here reflects the manual // override layered on top, not TOFU's own verdict. type DeviceInfo struct { ID DeviceID Trusted bool Fingerprint string } // Backend is the minimal contract a pluggable E2EE scheme must implement. type Backend interface { // Name identifies the backend for config/logging (e.g. "omemo"). Name() string // Namespaces lists what this backend wants advertised/served. Namespaces() Namespaces // Variants lists every wire-format variant this backend can // simultaneously serve/negotiate right now (e.g. all three OMEMO // namespaces, or fewer if this build lacks support for some of them - // see libsignal.ProtocolV4Supported). All variants are always // servable/fetchable regardless of any given chat's negotiated state // (see NegotiatedVariant) - "supported" is a deploy-time/build-time // property, not a per-chat one. Variants() []Variant // DefaultVariant is what a brand new chat (no negotiation evidence // yet) starts out using for outgoing content - e.g. OMEMO's omemo0, // the most widely deployed variant. DefaultVariant() Variant // NegotiatedVariant is the wire-format variant currently in use for // owner's (an OwnedPeer) outgoing content: DefaultVariant until an // inbound message from owner's peer is decrypted, at which point it // tracks whatever variant that peer's own client is actually using // (see Decrypt). Implementations may only be able to fix this at // session establishment time, not change it later for an // already-established chat - see e2ee/omemo.Backend.NegotiatedVariant's // doc comment for a concrete example of why. NegotiatedVariant(owner PeerID) (Variant, error) // DeviceListNode and BundleNode name the PEP node one of this backend's // own device-list/bundle documents is published/fetched under, for the // given variant. Note BundleNode's return value may or may not depend // on device - some backends address every device's bundle under one // shared node (item id = device), others use a distinct node per // device; callers (e2ee/fetch.go, and the xmpp package's PEP-serving IQ // handlers) must not assume either shape. DeviceListNode(variant Variant) string BundleNode(variant Variant, device DeviceID) string // EnsureIdentity creates (idempotently) a local cryptographic identity // for a PeerID this gateway owns (a bridged chat pseudo-JID - built via // OwnedPeer, since a chat pseudo-JID alone isn't globally unique across // telegabber's multiple bridged Telegram logins), generating and // persisting whatever key material the backend needs. One identity // serves every Variant simultaneously. Every other method that touches // identity/prekey material calls this internally too (it's cheap and // idempotent), so callers never strictly need to call it themselves - // it's exposed mainly for a future eager-bootstrap use case (e.g. // pre-creating identities for every personal chat when an account's // OMEMO toggle is turned on, instead of lazily on first use). EnsureIdentity(peer PeerID) error // OwnDevice is this backend's own (and only) published device id for // any owned peer - telegabber only ever needs one device per bridged // chat identity (see EnsureIdentity's doc comment), so unlike Devices // (which lists a remote peer's possibly-many devices) this takes no // arguments and returns no error. OwnDevice() DeviceID // PublishedIdentity returns the material this gateway must SERVE to // pubsub GET requests for its own PeerID (an OwnedPeer - see // EnsureIdentity) device list under the given variant, pre-marshaled to // opaque bytes the xmpp layer wraps in /. PublishedIdentity(peer PeerID, variant Variant) (DeviceListDoc, error) // PublishedBundle is the same, for one specific device's bundle node. PublishedBundle(peer PeerID, device DeviceID, variant Variant) (BundleDoc, error) // IngestRemoteDeviceList / IngestRemoteBundle feed the results of an // outbound PEP fetch (device-list, then per-device bundle) for a real // remote peer (owner is an OwnedPeer identifying which of this // gateway's identities is doing the fetching; peer is the remote's // plain bare JID, no scoping needed) into the backend's store, // establishing/refreshing sessions as needed (trust decisions happen // inside here). IngestRemoteDeviceList returns the device ids it found, // so a caller like e2ee/fetch.go knows which bundles to fetch next // without needing to parse the backend-specific document itself. IngestRemoteDeviceList(owner PeerID, peer PeerID, doc DeviceListDoc) ([]DeviceID, error) IngestRemoteBundle(owner PeerID, peer PeerID, device DeviceID, doc BundleDoc) error // Encrypt produces an opaque envelope addressed to every known device // of every given recipient, ready to be embedded in a stanza by the // xmpp layer. from is the identity doing the encrypting (an OwnedPeer - // see EnsureIdentity); to are the real remote peers' plain bare JIDs. Encrypt(from PeerID, to []PeerID, plaintext []byte) (Envelope, error) // Decrypt consumes an inbound envelope addressed to "to" (an // OwnedPeer - see EnsureIdentity) from "from" (the real remote peer's // plain bare JID) and returns plaintext. A successful decrypt also // updates "to"'s NegotiatedVariant to whichever variant this envelope // actually used - see NegotiatedVariant's doc comment. Decrypt(from PeerID, to PeerID, env Envelope) ([]byte, error) // Devices lists peer's known devices, as seen from owner's (an // OwnedPeer) point of view - for trust-listing ad-hoc commands. Devices(owner PeerID, peer PeerID) ([]DeviceInfo, error) // Enabled and SetEnabled track the per-chat "OMEMO is active" sticky // flag (the auto-upgrade trigger: set the first time an inbound // message decrypts successfully for owner, read by the outbound // encrypt hook alongside the account-wide config toggle). owner is an // OwnedPeer; a chat that was never marked active reports false, not an // error. Enabled(owner PeerID) (bool, error) SetEnabled(owner PeerID, enabled bool) error // Override, SetOverride, and ClearOverride manage owner's (an // OwnedPeer) manual on/off override of the account-wide Mode (see // ShouldEncrypt) - a per-chat "/omemo on"/"/omemo off" that wins // regardless of the global setting, until cleared. isSet is false when // no override has been set (follow the account-wide mode instead). Override(owner PeerID) (on bool, isSet bool, err error) SetOverride(owner PeerID, on bool) error ClearOverride(owner PeerID) error // BlockDevice and UnblockDevice manually override one of peer's // devices' TOFU trust, as seen from owner's (an OwnedPeer) point of // view - TOFU itself always stays on (a device's first-seen key is // always trusted automatically), this only ever narrows it further. A // blocked device's messages are rejected by Decrypt and skipped by // Encrypt, regardless of any session already established with it - see // Devices' Trusted field for the resulting per-device state. BlockDevice(owner PeerID, peer PeerID, device DeviceID) error UnblockDevice(owner PeerID, peer PeerID, device DeviceID) error // Close flushes and releases whatever storage/resources this backend // holds (e.g. the underlying Badger DB), for a clean process shutdown - // mirrors gateway.IdsDB.Close()'s role for the id-mapping store. Close() error }