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