openmls/group/mls_group/
mod.rs

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