openmls/group/mls_group/
mod.rs

1//! MLS Group
2//!
3//! This module contains [`MlsGroup`] and its submodules.
4//!
5
6use past_secrets::MessageSecretsStore;
7use proposal_store::ProposalQueue;
8use serde::{Deserialize, Serialize};
9use tls_codec::Serialize as _;
10
11#[cfg(test)]
12use crate::treesync::node::leaf_node::TreePosition;
13
14use super::proposal_store::{ProposalStore, QueuedProposal};
15use crate::{
16    binary_tree::array_representation::LeafNodeIndex,
17    ciphersuite::{hash_ref::ProposalRef, signable::Signable},
18    credentials::Credential,
19    error::LibraryError,
20    framing::{mls_auth_content::AuthenticatedContent, *},
21    group::{
22        CreateGroupContextExtProposalError, Extension, ExtensionType, Extensions,
23        ExternalPubExtension, GroupContext, GroupEpoch, GroupId, MlsGroupJoinConfig,
24        MlsGroupStateError, OutgoingWireFormatPolicy, PublicGroup, RatchetTreeExtension,
25        RequiredCapabilitiesExtension, StagedCommit,
26    },
27    key_packages::KeyPackageBundle,
28    messages::{
29        group_info::{GroupInfo, GroupInfoTBS, VerifiableGroupInfo},
30        proposals::*,
31        ConfirmationTag, GroupSecrets, Welcome,
32    },
33    schedule::{
34        message_secrets::MessageSecrets,
35        psk::{load_psks, store::ResumptionPskStore, PskSecret},
36        GroupEpochSecrets, JoinerSecret, KeySchedule,
37    },
38    storage::{OpenMlsProvider, StorageProvider},
39    treesync::{
40        node::{encryption_keys::EncryptionKeyPair, leaf_node::LeafNode},
41        RatchetTree,
42    },
43    versions::ProtocolVersion,
44};
45use openmls_traits::{signatures::Signer, storage::StorageProvider as _, types::Ciphersuite};
46
47// Private
48mod application;
49mod exporting;
50mod updates;
51
52use config::*;
53
54// Crate
55pub(crate) mod builder;
56pub(crate) mod commit_builder;
57pub(crate) mod config;
58pub(crate) mod creation;
59pub(crate) mod errors;
60pub(crate) mod membership;
61pub(crate) mod past_secrets;
62pub(crate) mod processing;
63pub(crate) mod proposal;
64pub(crate) mod proposal_store;
65pub(crate) mod staged_commit;
66
67// Tests
68#[cfg(test)]
69pub(crate) mod tests_and_kats;
70
71#[derive(Debug)]
72pub(crate) struct CreateCommitResult {
73    pub(crate) commit: AuthenticatedContent,
74    pub(crate) welcome_option: Option<Welcome>,
75    pub(crate) staged_commit: StagedCommit,
76    pub(crate) group_info: Option<GroupInfo>,
77}
78
79/// A member in the group is identified by this [`Member`] struct.
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81pub struct Member {
82    /// The member's leaf index in the ratchet tree.
83    pub index: LeafNodeIndex,
84    /// The member's credential.
85    pub credential: Credential,
86    /// The member's public HPHKE encryption key.
87    pub encryption_key: Vec<u8>,
88    /// The member's public signature key.
89    pub signature_key: Vec<u8>,
90}
91
92impl Member {
93    /// Create new member.
94    pub fn new(
95        index: LeafNodeIndex,
96        encryption_key: Vec<u8>,
97        signature_key: Vec<u8>,
98        credential: Credential,
99    ) -> Self {
100        Self {
101            index,
102            encryption_key,
103            signature_key,
104            credential,
105        }
106    }
107}
108
109/// Pending Commit state. Differentiates between Commits issued by group members
110/// and External Commits.
111#[derive(Debug, Serialize, Deserialize)]
112#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
113pub enum PendingCommitState {
114    /// Commit from a group member
115    Member(StagedCommit),
116    /// Commit from an external joiner
117    External(StagedCommit),
118}
119
120impl PendingCommitState {
121    /// Returns a reference to the [`StagedCommit`] contained in the
122    /// [`PendingCommitState`] enum.
123    pub(crate) fn staged_commit(&self) -> &StagedCommit {
124        match self {
125            PendingCommitState::Member(pc) => pc,
126            PendingCommitState::External(pc) => pc,
127        }
128    }
129}
130
131impl From<PendingCommitState> for StagedCommit {
132    fn from(pcs: PendingCommitState) -> Self {
133        match pcs {
134            PendingCommitState::Member(pc) => pc,
135            PendingCommitState::External(pc) => pc,
136        }
137    }
138}
139
140/// [`MlsGroupState`] determines the state of an [`MlsGroup`]. The different
141/// states and their transitions are as follows:
142///
143/// * [`MlsGroupState::Operational`]: This is the main state of the group, which
144///   allows access to all of its functionality, (except merging pending commits,
145///   see the [`MlsGroupState::PendingCommit`] for more information) and it's the
146///   state the group starts in (except when created via
147///   [`MlsGroup::external_commit_builder()`], see the functions documentation for
148///   more information). From this `Operational`, the group state can either
149///   transition to [`MlsGroupState::Inactive`], when it processes a commit that
150///   removes this client from the group, or to [`MlsGroupState::PendingCommit`],
151///   when this client creates a commit.
152///
153/// * [`MlsGroupState::Inactive`]: A group can enter this state from any other
154///   state when it processes a commit that removes this client from the group.
155///   This is a terminal state that the group can not exit from. If the clients
156///   wants to re-join the group, it can either be added by a group member or it
157///   can join via external commit.
158///
159/// * [`MlsGroupState::PendingCommit`]: This state is split into two possible
160///   sub-states, one for each Commit type:
161///   [`PendingCommitState::Member`] and [`PendingCommitState::External`]:
162///
163///   * If the client creates a commit for this group, the `PendingCommit` state
164///     is entered with [`PendingCommitState::Member`] and with the [`StagedCommit`] as
165///     additional state variable. In this state, it can perform the same
166///     operations as in the [`MlsGroupState::Operational`], except that it cannot
167///     create proposals or commits. However, it can merge or clear the stored
168///     [`StagedCommit`], where both actions result in a transition to the
169///     [`MlsGroupState::Operational`]. Additionally, if a commit from another
170///     group member is processed, the own pending commit is also cleared and
171///     either the `Inactive` state is entered (if this client was removed from
172///     the group as part of the processed commit), or the `Operational` state is
173///     entered.
174///
175///   * A group can enter the [`PendingCommitState::External`] sub-state only as
176///     the initial state when the group is created via
177///     [`MlsGroup::external_commit_builder()`]. In contrast to the
178///     [`PendingCommitState::Member`] `PendingCommit` state, the only possible
179///     functionality that can be used is the [`MlsGroup::merge_pending_commit()`]
180///     function, which merges the pending external commit and transitions the
181///     state to [`MlsGroupState::PendingCommit`]. For more information on the
182///     external commit process, see [`MlsGroup::external_commit_builder()`] or
183///     Section 11.2.1 of the MLS specification.
184#[derive(Debug, Serialize, Deserialize)]
185#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
186pub enum MlsGroupState {
187    /// There is currently a pending Commit that hasn't been merged yet.
188    PendingCommit(Box<PendingCommitState>),
189    /// The group state is in an opertaional state, where new messages and Commits can be created.
190    Operational,
191    /// The group is inactive because the member has been removed.
192    Inactive,
193}
194
195/// A `MlsGroup` represents an MLS group with a high-level API. The API exposes
196/// high level functions to manage a group by adding/removing members, get the
197/// current member list, etc.
198///
199/// The API is modeled such that it can serve as a direct interface to the
200/// Delivery Service. Functions that modify the public state of the group will
201/// return a `Vec<MLSMessageOut>` that can be sent to the Delivery Service
202/// directly. Conversely, incoming messages from the Delivery Service can be fed
203/// into [process_message()](`MlsGroup::process_message()`).
204///
205/// An `MlsGroup` has an internal queue of pending proposals that builds up as
206/// new messages are processed. When creating proposals, those messages are not
207/// automatically appended to this queue, instead they have to be processed
208/// again through [process_message()](`MlsGroup::process_message()`). This
209/// allows the Delivery Service to reject them (e.g. if they reference the wrong
210/// epoch).
211///
212/// If incoming messages or applied operations are semantically or syntactically
213/// incorrect, an error event will be returned with a corresponding error
214/// message and the state of the group will remain unchanged.
215///
216/// An `MlsGroup` has an internal state variable determining if it is active or
217/// inactive, as well as if it has a pending commit. See [`MlsGroupState`] for
218/// more information.
219#[derive(Debug)]
220#[cfg_attr(feature = "test-utils", derive(Clone, PartialEq))]
221pub struct MlsGroup {
222    /// The group configuration. See [`MlsGroupJoinConfig`] for more information.
223    mls_group_config: MlsGroupJoinConfig,
224    /// The public state of the group.
225    public_group: PublicGroup,
226    /// Epoch-specific secrets of the group.
227    group_epoch_secrets: GroupEpochSecrets,
228    /// The own leaf index in the ratchet tree.
229    own_leaf_index: LeafNodeIndex,
230    /// A [`MessageSecretsStore`] that stores message secrets.
231    /// By default this store has the length of 1, i.e. only the [`MessageSecrets`]
232    /// of the current epoch is kept.
233    /// If more secrets from past epochs should be kept in order to be
234    /// able to decrypt application messages from previous epochs, the size of
235    /// the store must be increased through [`max_past_epochs()`].
236    message_secrets_store: MessageSecretsStore,
237    // Resumption psk store. This is where the resumption psks are kept in a rollover list.
238    resumption_psk_store: ResumptionPskStore,
239    // Own [`LeafNode`]s that were created for update proposals and that
240    // are needed in case an update proposal is committed by another group
241    // member. The vector is emptied after every epoch change.
242    own_leaf_nodes: Vec<LeafNode>,
243    // Additional authenticated data (AAD) for the next outgoing message. This
244    // is ephemeral and will be reset by every API call that successfully
245    // returns an [`MlsMessageOut`].
246    aad: Vec<u8>,
247    // A variable that indicates the state of the group. See [`MlsGroupState`]
248    // for more information.
249    group_state: MlsGroupState,
250}
251
252impl MlsGroup {
253    // === Configuration ===
254
255    /// Returns the configuration.
256    pub fn configuration(&self) -> &MlsGroupJoinConfig {
257        &self.mls_group_config
258    }
259
260    /// Sets the configuration.
261    pub fn set_configuration<Storage: StorageProvider>(
262        &mut self,
263        storage: &Storage,
264        mls_group_config: &MlsGroupJoinConfig,
265    ) -> Result<(), Storage::Error> {
266        self.mls_group_config = mls_group_config.clone();
267        storage.write_mls_join_config(self.group_id(), mls_group_config)
268    }
269
270    /// Sets the additional authenticated data (AAD) for the next outgoing
271    /// message. This is ephemeral and will be reset by every API call that
272    /// successfully returns an [`MlsMessageOut`].
273    pub fn set_aad(&mut self, aad: Vec<u8>) {
274        self.aad = aad;
275    }
276
277    /// Returns the additional authenticated data (AAD) for the next outgoing
278    /// message.
279    pub fn aad(&self) -> &[u8] {
280        &self.aad
281    }
282
283    // === Advanced functions ===
284
285    /// Returns the group's ciphersuite.
286    pub fn ciphersuite(&self) -> Ciphersuite {
287        self.public_group.ciphersuite()
288    }
289
290    /// Get confirmation tag.
291    pub fn confirmation_tag(&self) -> &ConfirmationTag {
292        self.public_group.confirmation_tag()
293    }
294
295    /// Returns whether the own client is still a member of the group or if it
296    /// was already evicted
297    pub fn is_active(&self) -> bool {
298        !matches!(self.group_state, MlsGroupState::Inactive)
299    }
300
301    /// Returns own credential. If the group is inactive, it returns a
302    /// `UseAfterEviction` error.
303    pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
304        if !self.is_active() {
305            return Err(MlsGroupStateError::UseAfterEviction);
306        }
307        self.public_group
308            .leaf(self.own_leaf_index())
309            .map(|node| node.credential())
310            .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
311    }
312
313    /// Returns the leaf index of the client in the tree owning this group.
314    pub fn own_leaf_index(&self) -> LeafNodeIndex {
315        self.own_leaf_index
316    }
317
318    /// Returns the leaf node of the client in the tree owning this group.
319    pub fn own_leaf_node(&self) -> Option<&LeafNode> {
320        self.public_group().leaf(self.own_leaf_index())
321    }
322
323    /// Returns the group ID.
324    pub fn group_id(&self) -> &GroupId {
325        self.public_group.group_id()
326    }
327
328    /// Returns the epoch.
329    pub fn epoch(&self) -> GroupEpoch {
330        self.public_group.group_context().epoch()
331    }
332
333    /// Returns an `Iterator` over pending proposals.
334    pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
335        self.proposal_store().proposals()
336    }
337
338    /// Returns a reference to the [`StagedCommit`] of the most recently created
339    /// commit. If there was no commit created in this epoch, either because
340    /// this commit or another commit was merged, it returns `None`.
341    pub fn pending_commit(&self) -> Option<&StagedCommit> {
342        match self.group_state {
343            MlsGroupState::PendingCommit(ref pending_commit_state) => {
344                Some(pending_commit_state.staged_commit())
345            }
346            MlsGroupState::Operational => None,
347            MlsGroupState::Inactive => None,
348        }
349    }
350
351    /// Sets the `group_state` to [`MlsGroupState::Operational`], thus clearing
352    /// any potentially pending commits.
353    ///
354    /// Note that this has no effect if the group was created through an external commit and
355    /// the resulting external commit has not been merged yet. For more
356    /// information, see [`MlsGroup::external_commit_builder()`].
357    ///
358    /// Use with caution! This function should only be used if it is clear that
359    /// the pending commit will not be used in the group. In particular, if a
360    /// pending commit is later accepted by the group, this client will lack the
361    /// key material to encrypt or decrypt group messages.
362    pub fn clear_pending_commit<Storage: StorageProvider>(
363        &mut self,
364        storage: &Storage,
365    ) -> Result<(), Storage::Error> {
366        match self.group_state {
367            MlsGroupState::PendingCommit(ref pending_commit_state) => {
368                if let PendingCommitState::Member(_) = **pending_commit_state {
369                    self.group_state = MlsGroupState::Operational;
370                    storage.write_group_state(self.group_id(), &self.group_state)
371                } else {
372                    Ok(())
373                }
374            }
375            MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
376        }
377    }
378
379    /// Clear the pending proposals, if the proposal store is not empty.
380    ///
381    /// Warning: Once the pending proposals are cleared it will be impossible to process
382    /// a Commit message that references those proposals. Only use this
383    /// function as a last resort, e.g. when a call to
384    /// `MlsGroup::commit_to_pending_proposals` fails.
385    pub fn clear_pending_proposals<Storage: StorageProvider>(
386        &mut self,
387        storage: &Storage,
388    ) -> Result<(), Storage::Error> {
389        // If the proposal store is not empty...
390        if !self.proposal_store().is_empty() {
391            // Empty the proposal store
392            self.proposal_store_mut().empty();
393
394            // Clear proposals in storage
395            storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
396        }
397
398        Ok(())
399    }
400
401    /// Get a reference to the group context [`Extensions`] of this [`MlsGroup`].
402    pub fn extensions(&self) -> &Extensions {
403        self.public_group().group_context().extensions()
404    }
405
406    /// Returns the index of the sender of a staged, external commit.
407    pub fn ext_commit_sender_index(
408        &self,
409        commit: &StagedCommit,
410    ) -> Result<LeafNodeIndex, LibraryError> {
411        self.public_group().ext_commit_sender_index(commit)
412    }
413
414    // === Storage Methods ===
415
416    /// Loads the state of the group with given id from persisted state.
417    pub fn load<Storage: crate::storage::StorageProvider>(
418        storage: &Storage,
419        group_id: &GroupId,
420    ) -> Result<Option<MlsGroup>, Storage::Error> {
421        let public_group = PublicGroup::load(storage, group_id)?;
422        let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
423        let own_leaf_index = storage.own_leaf_index(group_id)?;
424        let message_secrets_store = storage.message_secrets(group_id)?;
425        let resumption_psk_store = storage.resumption_psk_store(group_id)?;
426        let mls_group_config = storage.mls_group_join_config(group_id)?;
427        let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
428        let group_state = storage.group_state(group_id)?;
429
430        let build = || -> Option<Self> {
431            Some(Self {
432                public_group: public_group?,
433                group_epoch_secrets: group_epoch_secrets?,
434                own_leaf_index: own_leaf_index?,
435                message_secrets_store: message_secrets_store?,
436                resumption_psk_store: resumption_psk_store?,
437                mls_group_config: mls_group_config?,
438                own_leaf_nodes,
439                aad: vec![],
440                group_state: group_state?,
441            })
442        };
443
444        Ok(build())
445    }
446
447    /// Remove the persisted state of this group from storage. Note that
448    /// signature key material is not managed by OpenMLS and has to be removed
449    /// from the storage provider separately (if desired).
450    pub fn delete<Storage: crate::storage::StorageProvider>(
451        &mut self,
452        storage: &Storage,
453    ) -> Result<(), Storage::Error> {
454        PublicGroup::delete(storage, self.group_id())?;
455        storage.delete_own_leaf_index(self.group_id())?;
456        storage.delete_group_epoch_secrets(self.group_id())?;
457        storage.delete_message_secrets(self.group_id())?;
458        storage.delete_all_resumption_psk_secrets(self.group_id())?;
459        storage.delete_group_config(self.group_id())?;
460        storage.delete_own_leaf_nodes(self.group_id())?;
461        storage.delete_group_state(self.group_id())?;
462        storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
463
464        self.proposal_store_mut().empty();
465        storage.delete_encryption_epoch_key_pairs(
466            self.group_id(),
467            &self.epoch(),
468            self.own_leaf_index().u32(),
469        )?;
470
471        Ok(())
472    }
473
474    // === Extensions ===
475
476    /// Exports the Ratchet Tree.
477    pub fn export_ratchet_tree(&self) -> RatchetTree {
478        self.public_group().export_ratchet_tree()
479    }
480}
481
482// Crate-public functions
483impl MlsGroup {
484    /// Get the required capabilities extension of this group.
485    pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
486        self.public_group.required_capabilities()
487    }
488
489    /// Get a reference to the group epoch secrets from the group
490    pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
491        &self.group_epoch_secrets
492    }
493
494    /// Get a reference to the message secrets from a group
495    pub(crate) fn message_secrets(&self) -> &MessageSecrets {
496        self.message_secrets_store.message_secrets()
497    }
498
499    /// Sets the size of the [`MessageSecretsStore`], i.e. the number of past
500    /// epochs to keep.
501    /// This allows application messages from previous epochs to be decrypted.
502    pub(crate) fn set_max_past_epochs(&mut self, max_past_epochs: usize) {
503        self.message_secrets_store.resize(max_past_epochs);
504    }
505
506    /// Get the message secrets. Either from the secrets store or from the group.
507    pub(crate) fn message_secrets_mut(
508        &mut self,
509        epoch: GroupEpoch,
510    ) -> Result<&mut MessageSecrets, SecretTreeError> {
511        if epoch < self.context().epoch() {
512            self.message_secrets_store
513                .secrets_for_epoch_mut(epoch)
514                .ok_or(SecretTreeError::TooDistantInThePast)
515        } else {
516            Ok(self.message_secrets_store.message_secrets_mut())
517        }
518    }
519
520    /// Get the message secrets. Either from the secrets store or from the group.
521    pub(crate) fn message_secrets_for_epoch(
522        &self,
523        epoch: GroupEpoch,
524    ) -> Result<&MessageSecrets, SecretTreeError> {
525        if epoch < self.context().epoch() {
526            self.message_secrets_store
527                .secrets_for_epoch(epoch)
528                .ok_or(SecretTreeError::TooDistantInThePast)
529        } else {
530            Ok(self.message_secrets_store.message_secrets())
531        }
532    }
533
534    /// Get the message secrets and leaves for the given epoch. Either from the
535    /// secrets store or from the group.
536    ///
537    /// Note that the leaves vector is empty for message secrets of the current
538    /// epoch. The caller can use treesync in this case.
539    pub(crate) fn message_secrets_and_leaves_mut(
540        &mut self,
541        epoch: GroupEpoch,
542    ) -> Result<(&mut MessageSecrets, &[Member]), SecretTreeError> {
543        if epoch < self.context().epoch() {
544            self.message_secrets_store
545                .secrets_and_leaves_for_epoch_mut(epoch)
546                .ok_or(SecretTreeError::TooDistantInThePast)
547        } else {
548            // No need for leaves here. The tree of the current epoch is
549            // available to the caller.
550            Ok((self.message_secrets_store.message_secrets_mut(), &[]))
551        }
552    }
553
554    /// Create a new group context extension proposal
555    pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
556        &self,
557        framing_parameters: FramingParameters,
558        extensions: Extensions,
559        signer: &impl Signer,
560    ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
561    {
562        // Ensure that the group supports all the extensions that are wanted.
563        let required_extension = extensions
564            .iter()
565            .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
566        if let Some(required_extension) = required_extension {
567            let required_capabilities = required_extension.as_required_capabilities_extension()?;
568            // Ensure we support all the capabilities.
569            self.own_leaf_node()
570                .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
571                .capabilities()
572                .supports_required_capabilities(required_capabilities)?;
573
574            // Ensure that all other leaf nodes support all the required
575            // extensions as well.
576            self.public_group()
577                .check_extension_support(required_capabilities.extension_types())?;
578        }
579        let proposal = GroupContextExtensionProposal::new(extensions);
580        let proposal = Proposal::GroupContextExtensions(proposal);
581        AuthenticatedContent::member_proposal(
582            framing_parameters,
583            self.own_leaf_index(),
584            proposal,
585            self.context(),
586            signer,
587        )
588        .map_err(|e| e.into())
589    }
590
591    // Encrypt an AuthenticatedContent into an PrivateMessage
592    pub(crate) fn encrypt<Provider: OpenMlsProvider>(
593        &mut self,
594        public_message: AuthenticatedContent,
595        provider: &Provider,
596    ) -> Result<PrivateMessage, MessageEncryptionError<Provider::StorageError>> {
597        let padding_size = self.configuration().padding_size();
598        let msg = PrivateMessage::try_from_authenticated_content(
599            provider.crypto(),
600            provider.rand(),
601            &public_message,
602            self.ciphersuite(),
603            self.message_secrets_store.message_secrets_mut(),
604            padding_size,
605        )?;
606
607        provider
608            .storage()
609            .write_message_secrets(self.group_id(), &self.message_secrets_store)
610            .map_err(MessageEncryptionError::StorageError)?;
611
612        Ok(msg)
613    }
614
615    /// Group framing parameters
616    pub(crate) fn framing_parameters(&self) -> FramingParameters<'_> {
617        FramingParameters::new(
618            &self.aad,
619            self.mls_group_config.wire_format_policy().outgoing(),
620        )
621    }
622
623    /// Returns a reference to the proposal store.
624    pub(crate) fn proposal_store(&self) -> &ProposalStore {
625        self.public_group.proposal_store()
626    }
627
628    /// Returns a mutable reference to the proposal store.
629    pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
630        self.public_group.proposal_store_mut()
631    }
632
633    /// Get the group context
634    pub(crate) fn context(&self) -> &GroupContext {
635        self.public_group.group_context()
636    }
637
638    /// Get the MLS version used in this group.
639    pub(crate) fn version(&self) -> ProtocolVersion {
640        self.public_group.version()
641    }
642
643    /// Resets the AAD.
644    #[inline]
645    pub(crate) fn reset_aad(&mut self) {
646        self.aad.clear();
647    }
648
649    /// Returns a reference to the public group.
650    pub(crate) fn public_group(&self) -> &PublicGroup {
651        &self.public_group
652    }
653}
654
655// Private methods of MlsGroup
656impl MlsGroup {
657    /// Store the given [`EncryptionKeyPair`]s in the `provider`'s key store
658    /// indexed by this group's [`GroupId`] and [`GroupEpoch`].
659    ///
660    /// Returns an error if access to the key store fails.
661    pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
662        &self,
663        store: &Storage,
664        keypair_references: &[EncryptionKeyPair],
665    ) -> Result<(), Storage::Error> {
666        store.write_encryption_epoch_key_pairs(
667            self.group_id(),
668            &self.context().epoch(),
669            self.own_leaf_index().u32(),
670            keypair_references,
671        )
672    }
673
674    /// Read the [`EncryptionKeyPair`]s of this group and its current
675    /// [`GroupEpoch`] from the `provider`'s storage.
676    ///
677    /// Returns an error if the lookup in the [`StorageProvider`] fails.
678    pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
679        &self,
680        store: &Storage,
681    ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
682        store.encryption_epoch_key_pairs(
683            self.group_id(),
684            &self.context().epoch(),
685            self.own_leaf_index().u32(),
686        )
687    }
688
689    /// Delete the [`EncryptionKeyPair`]s from the previous [`GroupEpoch`] from
690    /// the `provider`'s key store.
691    ///
692    /// Returns an error if access to the key store fails.
693    pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
694        &self,
695        store: &Storage,
696    ) -> Result<(), Storage::Error> {
697        store.delete_encryption_epoch_key_pairs(
698            self.group_id(),
699            &GroupEpoch::from(self.context().epoch().as_u64() - 1),
700            self.own_leaf_index().u32(),
701        )
702    }
703
704    /// Stores the state of this group. Only to be called from constructors to
705    /// store the initial state of the group.
706    pub(super) fn store<Storage: crate::storage::StorageProvider>(
707        &self,
708        storage: &Storage,
709    ) -> Result<(), Storage::Error> {
710        self.public_group.store(storage)?;
711        storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
712        storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
713        storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
714        storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
715        storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
716        storage.write_group_state(self.group_id(), &self.group_state)?;
717
718        Ok(())
719    }
720
721    /// Converts PublicMessage to MlsMessage. Depending on whether handshake
722    /// message should be encrypted, PublicMessage messages are encrypted to
723    /// PrivateMessage first.
724    fn content_to_mls_message(
725        &mut self,
726        mls_auth_content: AuthenticatedContent,
727        provider: &impl OpenMlsProvider,
728    ) -> Result<MlsMessageOut, LibraryError> {
729        let msg = match self.configuration().wire_format_policy().outgoing() {
730            OutgoingWireFormatPolicy::AlwaysPlaintext => {
731                let mut plaintext: PublicMessage = mls_auth_content.into();
732                // Set the membership tag only if the sender type is `Member`.
733                if plaintext.sender().is_member() {
734                    plaintext.set_membership_tag(
735                        provider.crypto(),
736                        self.ciphersuite(),
737                        self.message_secrets().membership_key(),
738                        self.message_secrets().serialized_context(),
739                    )?;
740                }
741                plaintext.into()
742            }
743            OutgoingWireFormatPolicy::AlwaysCiphertext => {
744                let ciphertext = self
745                    .encrypt(mls_auth_content, provider)
746                    // We can be sure the encryption will work because the plaintext was created by us
747                    .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
748                MlsMessageOut::from_private_message(ciphertext, self.version())
749            }
750        };
751        Ok(msg)
752    }
753
754    /// Check if the group is operational. Throws an error if the group is
755    /// inactive or if there is a pending commit.
756    fn is_operational(&self) -> Result<(), MlsGroupStateError> {
757        match self.group_state {
758            MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
759            MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
760            MlsGroupState::Operational => Ok(()),
761        }
762    }
763}
764
765// Methods used in tests
766impl MlsGroup {
767    #[cfg(any(feature = "test-utils", test))]
768    pub fn export_group_context(&self) -> &GroupContext {
769        self.context()
770    }
771
772    #[cfg(any(feature = "test-utils", test))]
773    pub fn tree_hash(&self) -> &[u8] {
774        self.public_group().group_context().tree_hash()
775    }
776
777    #[cfg(any(feature = "test-utils", test))]
778    pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
779        self.message_secrets_store.message_secrets_mut()
780    }
781
782    #[cfg(any(feature = "test-utils", test))]
783    pub fn print_ratchet_tree(&self, message: &str) {
784        println!("{}: {}", message, self.public_group().export_ratchet_tree());
785    }
786
787    #[cfg(any(feature = "test-utils", test))]
788    pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
789        self.public_group.context_mut()
790    }
791
792    #[cfg(test)]
793    pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
794        self.own_leaf_index = own_leaf_index;
795    }
796
797    #[cfg(test)]
798    pub(crate) fn own_tree_position(&self) -> TreePosition {
799        TreePosition::new(self.group_id().clone(), self.own_leaf_index())
800    }
801
802    #[cfg(test)]
803    pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
804        &self.message_secrets_store
805    }
806
807    #[cfg(test)]
808    pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
809        &self.resumption_psk_store
810    }
811
812    #[cfg(test)]
813    pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
814        self.public_group.set_group_context(group_context)
815    }
816}
817
818/// A [`StagedWelcome`] can be inspected and then turned into a [`MlsGroup`].
819/// This allows checking who authored the Welcome message.
820#[derive(Debug)]
821pub struct StagedWelcome {
822    // The group configuration. See [`MlsGroupJoinConfig`] for more information.
823    mls_group_config: MlsGroupJoinConfig,
824    public_group: PublicGroup,
825    group_epoch_secrets: GroupEpochSecrets,
826    own_leaf_index: LeafNodeIndex,
827
828    /// A [`MessageSecretsStore`] that stores message secrets.
829    /// By default this store has the length of 1, i.e. only the [`MessageSecrets`]
830    /// of the current epoch is kept.
831    /// If more secrets from past epochs should be kept in order to be
832    /// able to decrypt application messages from previous epochs, the size of
833    /// the store must be increased through [`max_past_epochs()`].
834    message_secrets_store: MessageSecretsStore,
835
836    /// Resumption psk store. This is where the resumption psks are kept in a rollover list.
837    resumption_psk_store: ResumptionPskStore,
838
839    /// The [`VerifiableGroupInfo`] from the [`Welcome`] message.
840    verifiable_group_info: VerifiableGroupInfo,
841
842    /// The key package bundle used for this welcome.
843    key_package_bundle: KeyPackageBundle,
844
845    /// If we got a path secret, these are the derived path keys.
846    path_keypairs: Option<Vec<EncryptionKeyPair>>,
847}
848
849/// A `Welcome` message that has been processed but not staged yet.
850///
851/// This may be used in order to retrieve information from the `Welcome` about
852/// the ratchet tree and PSKs.
853///
854/// Use `into_staged_welcome` to stage it into a [`StagedWelcome`].
855pub struct ProcessedWelcome {
856    // The group configuration. See [`MlsGroupJoinConfig`] for more information.
857    mls_group_config: MlsGroupJoinConfig,
858
859    // The following is the state after parsing the Welcome message, before actually
860    // building the group.
861    ciphersuite: Ciphersuite,
862    group_secrets: GroupSecrets,
863    key_schedule: crate::schedule::KeySchedule,
864    verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
865    resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
866    key_package_bundle: KeyPackageBundle,
867}