pub struct GroupManager { /* private fields */ }Expand description
Manages MLS groups for encrypted messaging.
Implementations§
Source§impl GroupManager
impl GroupManager
Sourcepub fn new(storage: Arc<dyn MlsStorage>, provider: MlsProvider) -> Self
pub fn new(storage: Arc<dyn MlsStorage>, provider: MlsProvider) -> Self
Creates a new group manager.
Sourcepub fn set_enforce_admin_commits(&mut self, enforce: bool)
pub fn set_enforce_admin_commits(&mut self, enforce: bool)
Enables or disables receive-side authorization of membership commits.
Off by default, and changing that is a group-partitioning decision, not a hardening toggle. Refusing a commit means not merging it: our epoch stays behind every member who did merge, which MLS cannot heal — the app has to re-invite us. Enforcement is therefore safe only if every member reaches the same verdict, and the admin overlay is replicated best-effort (role changes ride unreconciled mesh notifications; joiners get a point-in-time snapshot). Members whose role maps merely disagree will partition each other with no attacker involved.
authorize_membership_commit (private) fails open on absent knowledge
to keep the common lag case harmless, but it cannot detect divergent
knowledge. Enable this only for a closed deployment that controls role
distribution.
Sourcepub fn provider(&self) -> &MlsProvider
pub fn provider(&self) -> &MlsProvider
Returns a reference to the crypto provider.
Sourcepub fn create_group(
&self,
group_id: &GroupId,
credential_with_key: &CredentialWithKey,
signer: &impl Signer,
) -> Result<MlsGroup>
pub fn create_group( &self, group_id: &GroupId, credential_with_key: &CredentialWithKey, signer: &impl Signer, ) -> Result<MlsGroup>
Creates a new MLS group.
Sourcepub fn load_group(&self, group_id: &GroupId) -> Result<Option<MlsGroup>>
pub fn load_group(&self, group_id: &GroupId) -> Result<Option<MlsGroup>>
Loads a group from storage.
Sourcepub fn save_group(&self, group_id: &GroupId, group: &MlsGroup) -> Result<()>
pub fn save_group(&self, group_id: &GroupId, group: &MlsGroup) -> Result<()>
Saves a group marker to storage.
OpenMLS persists group state through the provider during mutations. This marker is kept in our storage layer for listing/enumeration.
Sourcepub fn delete_group(&self, group_id: &GroupId) -> Result<()>
pub fn delete_group(&self, group_id: &GroupId) -> Result<()>
Deletes a group from storage, including all OpenMLS provider state.
Sourcepub fn list_groups(&self) -> Result<Vec<GroupId>>
pub fn list_groups(&self) -> Result<Vec<GroupId>>
Lists all groups.
Stored keys that fail group-id validation (possible only for state written before validation was enforced) are skipped with a warning rather than resurrected as live group ids.
Sourcepub fn add_member(
&self,
group: &mut MlsGroup,
key_package: KeyPackage,
signer: &impl Signer,
) -> Result<(MlsMessageOut, MlsMessageOut)>
pub fn add_member( &self, group: &mut MlsGroup, key_package: KeyPackage, signer: &impl Signer, ) -> Result<(MlsMessageOut, MlsMessageOut)>
Adds a member to a group using their key package.
Sourcepub fn remove_member(
&self,
group: &mut MlsGroup,
member_indices: &[LeafNodeIndex],
signer: &impl Signer,
) -> Result<MlsMessageOut>
pub fn remove_member( &self, group: &mut MlsGroup, member_indices: &[LeafNodeIndex], signer: &impl Signer, ) -> Result<MlsMessageOut>
Removes members from a group.
Takes every leaf to remove in one commit rather than one leaf per call, because removing “that member” has to mean all of them or the peer stays in the group holding live keys while every roster read shows them gone.
Two leaves for one identity cannot arrive through the wire gates — MLS
requires unique signature keys and the binding ties credential to key —
but they can exist in a tampered provider store, where a forged leaf
claims a peer’s address while carrying the attacker’s key. See
crate::MlsManager::remove_group_member for the full argument; do not
restate it here, and do not reduce this to a first match.
Sourcepub fn encrypt_message(
&self,
group: &mut MlsGroup,
plaintext: &[u8],
signer: &impl Signer,
) -> Result<MlsMessageOut>
pub fn encrypt_message( &self, group: &mut MlsGroup, plaintext: &[u8], signer: &impl Signer, ) -> Result<MlsMessageOut>
Creates an encrypted application message.
Sourcepub fn decrypt_message(
&self,
group: &mut MlsGroup,
group_id: &GroupId,
message: MlsMessageIn,
claimed_sender: &str,
) -> Result<Option<Vec<u8>>>
pub fn decrypt_message( &self, group: &mut MlsGroup, group_id: &GroupId, message: MlsMessageIn, claimed_sender: &str, ) -> Result<Option<Vec<u8>>>
Decrypts an incoming MLS message.
Security (SEC-M1): claimed_sender is the identity the caller will
attribute this message to (the transport-level sender). It is
cross-checked against the MLS-authenticated credential of the
processed message before any plaintext is surfaced or any commit
is merged, so a group member cannot impersonate another member by
lying in the wire envelope.
Sourcepub fn join_group(
&self,
welcome: Welcome,
group_id: &GroupId,
) -> Result<MlsGroup>
pub fn join_group( &self, welcome: Welcome, group_id: &GroupId, ) -> Result<MlsGroup>
Joins a group using a Welcome message.
Sourcepub fn join_group_replacing(
&self,
welcome: Welcome,
group_id: &GroupId,
) -> Result<MlsGroup>
pub fn join_group_replacing( &self, welcome: Welcome, group_id: &GroupId, ) -> Result<MlsGroup>
Joins a group from a Welcome, replacing any existing group at the same
group_id. Non-destructive on the common failure (a duplicate
Welcome); best-effort — NOT atomic — on the rare storage failure.
Staging the incoming Welcome (StagedWelcome::new_from_welcome) is the
step that consumes the one-time key package, so it is the natural failure
point: if the key package is unavailable — e.g. a retransmitted Welcome we
already adopted, or a first-contact key-package race — staging returns
Err before we touch the existing group, which is therefore left
completely intact. This makes both-create adoption idempotent: a duplicate
Welcome is a safe no-op rather than a re-brick, the case this method exists
to handle.
Caveat — once staging succeeds the swap is not atomic. We delete the
prior group (to avoid orphaning its old-leaf epoch encryption keypairs,
which are keyed by (group_id, epoch, leaf_index); the adopted group
installs at a different leaf and so would not overwrite them) and then call
into_group, which performs several sequential, non-transactional storage
writes. If one of those fails (e.g. a Keychain/Keystore write error), the
old group is already gone and the new one is absent — a lost session. The
key package is spent by then, so the same Welcome cannot simply be retried;
recovery is a fresh key-package exchange. The caller distinguishes this
“lost group” outcome from a benign duplicate by observing has_session
(false here, true for a duplicate) and surfaces it as a session failure.
Sourcepub fn get_group_info(&self, group: &MlsGroup, group_id: &GroupId) -> GroupInfo
pub fn get_group_info(&self, group: &MlsGroup, group_id: &GroupId) -> GroupInfo
Gets information about a group.
Members whose leaf does not carry the address its own signature key
derives to are skipped with a warning, the same disposition
Self::list_groups gives a stored group id that fails validation.
Post-gates such a leaf cannot arrive over the wire, so one appearing here means local group state was written behind the SDK’s back. Keeping it out of the roster matters because the roster is not only what the app displays: it addresses the per-member fan-out, feeds the group’s rich-payload capability gate, and supplies the candidates for the address-ordered tiebreakers (leave election, admin auto-promotion, fork leader). A forged entry in it is not cosmetic.
Skipping rather than failing keeps a tampered store from taking the
whole group down, and the message it would have carried is refused
anyway — Self::verify_sender_leaf judges the same leaf at decrypt.
Skipping silently would be the mistake, though: this is the only seam
at which a leaf already seated in local state surfaces at all, and a
warn! reaches no app. Every other refusal in this family is reported.
So the count rides out on GroupInfo::unproven_members, and the
protocol layer turns a non-zero one into the same
GroupLeafIdentityUnproven warning the wire gates emit. The count and
not the identities, for the reason that field documents.
O(N) in the roster, against Self::verify_sender_leaf’s O(1): one
SHA-256 over 32 bytes plus a bech32m encode per member, per call, and
process_commit_core calls this twice per commit to derive a membership
delta. Immaterial next to the MLS crypto on the same path — microseconds
at a hundred members — but this is the expensive seam of the two, so do
not reach for it in a loop.
Auto Trait Implementations§
impl !RefUnwindSafe for GroupManager
impl !UnwindSafe for GroupManager
impl Freeze for GroupManager
impl Send for GroupManager
impl Sync for GroupManager
impl Unpin for GroupManager
impl UnsafeUnpin for GroupManager
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Declassify for T
impl<T> Declassify for T
type Declassified = T
fn declassify(self) -> T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more