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#[cfg(feature = "extensions-draft-08")]
71pub(crate) mod app_ephemeral;
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::external_commit_builder()`], 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::external_commit_builder()`]. 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::external_commit_builder()`] 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    /// The state of the Application Exporter. See the MLS Extensions Draft 08
257    /// for more information. This is `None` if an old OpenMLS group state was
258    /// loaded and has not yet merged a commit.
259    #[cfg(feature = "extensions-draft-08")]
260    application_export_tree: Option<ApplicationExportTree>,
261}
262
263impl MlsGroup {
264    // === Configuration ===
265
266    /// Returns the configuration.
267    pub fn configuration(&self) -> &MlsGroupJoinConfig {
268        &self.mls_group_config
269    }
270
271    /// Sets the configuration.
272    pub fn set_configuration<Storage: StorageProvider>(
273        &mut self,
274        storage: &Storage,
275        mls_group_config: &MlsGroupJoinConfig,
276    ) -> Result<(), Storage::Error> {
277        self.mls_group_config = mls_group_config.clone();
278        storage.write_mls_join_config(self.group_id(), mls_group_config)
279    }
280
281    /// Sets the additional authenticated data (AAD) for the next outgoing
282    /// message. This is ephemeral and will be reset by every API call that
283    /// successfully returns an [`MlsMessageOut`].
284    pub fn set_aad(&mut self, aad: Vec<u8>) {
285        self.aad = aad;
286    }
287
288    /// Returns the additional authenticated data (AAD) for the next outgoing
289    /// message.
290    pub fn aad(&self) -> &[u8] {
291        &self.aad
292    }
293
294    // === Advanced functions ===
295
296    /// Returns the group's ciphersuite.
297    pub fn ciphersuite(&self) -> Ciphersuite {
298        self.public_group.ciphersuite()
299    }
300
301    /// Get confirmation tag.
302    pub fn confirmation_tag(&self) -> &ConfirmationTag {
303        self.public_group.confirmation_tag()
304    }
305
306    /// Returns whether the own client is still a member of the group or if it
307    /// was already evicted
308    pub fn is_active(&self) -> bool {
309        !matches!(self.group_state, MlsGroupState::Inactive)
310    }
311
312    /// Returns own credential. If the group is inactive, it returns a
313    /// `UseAfterEviction` error.
314    pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
315        if !self.is_active() {
316            return Err(MlsGroupStateError::UseAfterEviction);
317        }
318        self.public_group
319            .leaf(self.own_leaf_index())
320            .map(|node| node.credential())
321            .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
322    }
323
324    /// Returns the leaf index of the client in the tree owning this group.
325    pub fn own_leaf_index(&self) -> LeafNodeIndex {
326        self.own_leaf_index
327    }
328
329    /// Returns the leaf node of the client in the tree owning this group.
330    pub fn own_leaf_node(&self) -> Option<&LeafNode> {
331        self.public_group().leaf(self.own_leaf_index())
332    }
333
334    /// Returns the group ID.
335    pub fn group_id(&self) -> &GroupId {
336        self.public_group.group_id()
337    }
338
339    /// Returns the epoch.
340    pub fn epoch(&self) -> GroupEpoch {
341        self.public_group.group_context().epoch()
342    }
343
344    /// Returns an `Iterator` over pending proposals.
345    pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
346        self.proposal_store().proposals()
347    }
348
349    /// Returns a reference to the [`StagedCommit`] of the most recently created
350    /// commit. If there was no commit created in this epoch, either because
351    /// this commit or another commit was merged, it returns `None`.
352    pub fn pending_commit(&self) -> Option<&StagedCommit> {
353        match self.group_state {
354            MlsGroupState::PendingCommit(ref pending_commit_state) => {
355                Some(pending_commit_state.staged_commit())
356            }
357            MlsGroupState::Operational => None,
358            MlsGroupState::Inactive => None,
359        }
360    }
361
362    /// Sets the `group_state` to [`MlsGroupState::Operational`], thus clearing
363    /// any potentially pending commits.
364    ///
365    /// Note that this has no effect if the group was created through an external commit and
366    /// the resulting external commit has not been merged yet. For more
367    /// information, see [`MlsGroup::external_commit_builder()`].
368    ///
369    /// Use with caution! This function should only be used if it is clear that
370    /// the pending commit will not be used in the group. In particular, if a
371    /// pending commit is later accepted by the group, this client will lack the
372    /// key material to encrypt or decrypt group messages.
373    pub fn clear_pending_commit<Storage: StorageProvider>(
374        &mut self,
375        storage: &Storage,
376    ) -> Result<(), Storage::Error> {
377        match self.group_state {
378            MlsGroupState::PendingCommit(ref pending_commit_state) => {
379                if let PendingCommitState::Member(_) = **pending_commit_state {
380                    self.group_state = MlsGroupState::Operational;
381                    storage.write_group_state(self.group_id(), &self.group_state)
382                } else {
383                    Ok(())
384                }
385            }
386            MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
387        }
388    }
389
390    /// Clear the pending proposals, if the proposal store is not empty.
391    ///
392    /// Warning: Once the pending proposals are cleared it will be impossible to process
393    /// a Commit message that references those proposals. Only use this
394    /// function as a last resort, e.g. when a call to
395    /// `MlsGroup::commit_to_pending_proposals` fails.
396    pub fn clear_pending_proposals<Storage: StorageProvider>(
397        &mut self,
398        storage: &Storage,
399    ) -> Result<(), Storage::Error> {
400        // If the proposal store is not empty...
401        if !self.proposal_store().is_empty() {
402            // Empty the proposal store
403            self.proposal_store_mut().empty();
404
405            // Clear proposals in storage
406            storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
407        }
408
409        Ok(())
410    }
411
412    /// Get a reference to the group context [`Extensions`] of this [`MlsGroup`].
413    pub fn extensions(&self) -> &Extensions {
414        self.public_group().group_context().extensions()
415    }
416
417    /// Returns the index of the sender of a staged, external commit.
418    pub fn ext_commit_sender_index(
419        &self,
420        commit: &StagedCommit,
421    ) -> Result<LeafNodeIndex, LibraryError> {
422        self.public_group().ext_commit_sender_index(commit)
423    }
424
425    // === Storage Methods ===
426
427    /// Loads the state of the group with given id from persisted state.
428    pub fn load<Storage: crate::storage::StorageProvider>(
429        storage: &Storage,
430        group_id: &GroupId,
431    ) -> Result<Option<MlsGroup>, Storage::Error> {
432        let public_group = PublicGroup::load(storage, group_id)?;
433        let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
434        let own_leaf_index = storage.own_leaf_index(group_id)?;
435        let message_secrets_store = storage.message_secrets(group_id)?;
436        let resumption_psk_store = storage.resumption_psk_store(group_id)?;
437        let mls_group_config = storage.mls_group_join_config(group_id)?;
438        let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
439        let group_state = storage.group_state(group_id)?;
440        #[cfg(feature = "extensions-draft-08")]
441        let application_export_tree = storage.application_export_tree(group_id)?;
442
443        let build = || -> Option<Self> {
444            Some(Self {
445                public_group: public_group?,
446                group_epoch_secrets: group_epoch_secrets?,
447                own_leaf_index: own_leaf_index?,
448                message_secrets_store: message_secrets_store?,
449                resumption_psk_store: resumption_psk_store?,
450                mls_group_config: mls_group_config?,
451                own_leaf_nodes,
452                aad: vec![],
453                group_state: group_state?,
454                #[cfg(feature = "extensions-draft-08")]
455                application_export_tree,
456            })
457        };
458
459        Ok(build())
460    }
461
462    /// Remove the persisted state of this group from storage. Note that
463    /// signature key material is not managed by OpenMLS and has to be removed
464    /// from the storage provider separately (if desired).
465    pub fn delete<Storage: crate::storage::StorageProvider>(
466        &mut self,
467        storage: &Storage,
468    ) -> Result<(), Storage::Error> {
469        PublicGroup::delete(storage, self.group_id())?;
470        storage.delete_own_leaf_index(self.group_id())?;
471        storage.delete_group_epoch_secrets(self.group_id())?;
472        storage.delete_message_secrets(self.group_id())?;
473        storage.delete_all_resumption_psk_secrets(self.group_id())?;
474        storage.delete_group_config(self.group_id())?;
475        storage.delete_own_leaf_nodes(self.group_id())?;
476        storage.delete_group_state(self.group_id())?;
477        storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
478
479        #[cfg(feature = "extensions-draft-08")]
480        storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
481
482        self.proposal_store_mut().empty();
483        storage.delete_encryption_epoch_key_pairs(
484            self.group_id(),
485            &self.epoch(),
486            self.own_leaf_index().u32(),
487        )?;
488
489        Ok(())
490    }
491
492    // === Extensions ===
493
494    /// Exports the Ratchet Tree.
495    pub fn export_ratchet_tree(&self) -> RatchetTree {
496        self.public_group().export_ratchet_tree()
497    }
498}
499
500// Crate-public functions
501impl MlsGroup {
502    /// Get the required capabilities extension of this group.
503    pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
504        self.public_group.required_capabilities()
505    }
506
507    /// Get a reference to the group epoch secrets from the group
508    pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
509        &self.group_epoch_secrets
510    }
511
512    /// Get a reference to the message secrets from a group
513    pub(crate) fn message_secrets(&self) -> &MessageSecrets {
514        self.message_secrets_store.message_secrets()
515    }
516
517    /// Sets the size of the [`MessageSecretsStore`], i.e. the number of past
518    /// epochs to keep.
519    /// This allows application messages from previous epochs to be decrypted.
520    pub(crate) fn set_max_past_epochs(&mut self, max_past_epochs: usize) {
521        self.message_secrets_store.resize(max_past_epochs);
522    }
523
524    /// Get the message secrets. Either from the secrets store or from the group.
525    pub(crate) fn message_secrets_mut(
526        &mut self,
527        epoch: GroupEpoch,
528    ) -> Result<&mut MessageSecrets, SecretTreeError> {
529        if epoch < self.context().epoch() {
530            self.message_secrets_store
531                .secrets_for_epoch_mut(epoch)
532                .ok_or(SecretTreeError::TooDistantInThePast)
533        } else {
534            Ok(self.message_secrets_store.message_secrets_mut())
535        }
536    }
537
538    /// Get the message secrets. Either from the secrets store or from the group.
539    pub(crate) fn message_secrets_for_epoch(
540        &self,
541        epoch: GroupEpoch,
542    ) -> Result<&MessageSecrets, SecretTreeError> {
543        if epoch < self.context().epoch() {
544            self.message_secrets_store
545                .secrets_for_epoch(epoch)
546                .ok_or(SecretTreeError::TooDistantInThePast)
547        } else {
548            Ok(self.message_secrets_store.message_secrets())
549        }
550    }
551
552    /// Get the message secrets and leaves for the given epoch. Either from the
553    /// secrets store or from the group.
554    ///
555    /// Note that the leaves vector is empty for message secrets of the current
556    /// epoch. The caller can use treesync in this case.
557    pub(crate) fn message_secrets_and_leaves_mut(
558        &mut self,
559        epoch: GroupEpoch,
560    ) -> Result<(&mut MessageSecrets, &[Member]), SecretTreeError> {
561        if epoch < self.context().epoch() {
562            self.message_secrets_store
563                .secrets_and_leaves_for_epoch_mut(epoch)
564                .ok_or(SecretTreeError::TooDistantInThePast)
565        } else {
566            // No need for leaves here. The tree of the current epoch is
567            // available to the caller.
568            Ok((self.message_secrets_store.message_secrets_mut(), &[]))
569        }
570    }
571
572    /// Create a new group context extension proposal
573    pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
574        &self,
575        framing_parameters: FramingParameters,
576        extensions: Extensions,
577        signer: &impl Signer,
578    ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
579    {
580        // Ensure that the group supports all the extensions that are wanted.
581        let required_extension = extensions
582            .iter()
583            .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
584        if let Some(required_extension) = required_extension {
585            let required_capabilities = required_extension.as_required_capabilities_extension()?;
586            // Ensure we support all the capabilities.
587            self.own_leaf_node()
588                .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
589                .capabilities()
590                .supports_required_capabilities(required_capabilities)?;
591
592            // Ensure that all other leaf nodes support all the required
593            // extensions as well.
594            self.public_group()
595                .check_extension_support(required_capabilities.extension_types())?;
596        }
597        let proposal = GroupContextExtensionProposal::new(extensions);
598        let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
599        AuthenticatedContent::member_proposal(
600            framing_parameters,
601            self.own_leaf_index(),
602            proposal,
603            self.context(),
604            signer,
605        )
606        .map_err(|e| e.into())
607    }
608
609    // Encrypt an AuthenticatedContent into an PrivateMessage
610    pub(crate) fn encrypt<Provider: OpenMlsProvider>(
611        &mut self,
612        public_message: AuthenticatedContent,
613        provider: &Provider,
614    ) -> Result<PrivateMessage, MessageEncryptionError<Provider::StorageError>> {
615        let padding_size = self.configuration().padding_size();
616        let msg = PrivateMessage::try_from_authenticated_content(
617            provider.crypto(),
618            provider.rand(),
619            &public_message,
620            self.ciphersuite(),
621            self.message_secrets_store.message_secrets_mut(),
622            padding_size,
623        )?;
624
625        provider
626            .storage()
627            .write_message_secrets(self.group_id(), &self.message_secrets_store)
628            .map_err(MessageEncryptionError::StorageError)?;
629
630        Ok(msg)
631    }
632
633    /// Group framing parameters
634    pub(crate) fn framing_parameters(&self) -> FramingParameters<'_> {
635        FramingParameters::new(
636            &self.aad,
637            self.mls_group_config.wire_format_policy().outgoing(),
638        )
639    }
640
641    /// Returns a reference to the proposal store.
642    pub(crate) fn proposal_store(&self) -> &ProposalStore {
643        self.public_group.proposal_store()
644    }
645
646    /// Returns a mutable reference to the proposal store.
647    pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
648        self.public_group.proposal_store_mut()
649    }
650
651    /// Get the group context
652    pub(crate) fn context(&self) -> &GroupContext {
653        self.public_group.group_context()
654    }
655
656    /// Get the MLS version used in this group.
657    pub(crate) fn version(&self) -> ProtocolVersion {
658        self.public_group.version()
659    }
660
661    /// Resets the AAD.
662    #[inline]
663    pub(crate) fn reset_aad(&mut self) {
664        self.aad.clear();
665    }
666
667    /// Returns a reference to the public group.
668    pub(crate) fn public_group(&self) -> &PublicGroup {
669        &self.public_group
670    }
671}
672
673// Private methods of MlsGroup
674impl MlsGroup {
675    /// Store the given [`EncryptionKeyPair`]s in the `provider`'s key store
676    /// indexed by this group's [`GroupId`] and [`GroupEpoch`].
677    ///
678    /// Returns an error if access to the key store fails.
679    pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
680        &self,
681        store: &Storage,
682        keypair_references: &[EncryptionKeyPair],
683    ) -> Result<(), Storage::Error> {
684        store.write_encryption_epoch_key_pairs(
685            self.group_id(),
686            &self.context().epoch(),
687            self.own_leaf_index().u32(),
688            keypair_references,
689        )
690    }
691
692    /// Read the [`EncryptionKeyPair`]s of this group and its current
693    /// [`GroupEpoch`] from the `provider`'s storage.
694    ///
695    /// Returns an error if the lookup in the [`StorageProvider`] fails.
696    pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
697        &self,
698        store: &Storage,
699    ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
700        store.encryption_epoch_key_pairs(
701            self.group_id(),
702            &self.context().epoch(),
703            self.own_leaf_index().u32(),
704        )
705    }
706
707    /// Delete the [`EncryptionKeyPair`]s from the previous [`GroupEpoch`] from
708    /// the `provider`'s key store.
709    ///
710    /// Returns an error if access to the key store fails.
711    pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
712        &self,
713        store: &Storage,
714    ) -> Result<(), Storage::Error> {
715        store.delete_encryption_epoch_key_pairs(
716            self.group_id(),
717            &GroupEpoch::from(self.context().epoch().as_u64() - 1),
718            self.own_leaf_index().u32(),
719        )
720    }
721
722    /// Stores the state of this group. Only to be called from constructors to
723    /// store the initial state of the group.
724    pub(super) fn store<Storage: crate::storage::StorageProvider>(
725        &self,
726        storage: &Storage,
727    ) -> Result<(), Storage::Error> {
728        self.public_group.store(storage)?;
729        storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
730        storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
731        storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
732        storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
733        storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
734        storage.write_group_state(self.group_id(), &self.group_state)?;
735        #[cfg(feature = "extensions-draft-08")]
736        if let Some(application_export_tree) = &self.application_export_tree {
737            storage.write_application_export_tree(self.group_id(), application_export_tree)?;
738        }
739
740        Ok(())
741    }
742
743    /// Converts PublicMessage to MlsMessage. Depending on whether handshake
744    /// message should be encrypted, PublicMessage messages are encrypted to
745    /// PrivateMessage first.
746    fn content_to_mls_message(
747        &mut self,
748        mls_auth_content: AuthenticatedContent,
749        provider: &impl OpenMlsProvider,
750    ) -> Result<MlsMessageOut, LibraryError> {
751        let msg = match self.configuration().wire_format_policy().outgoing() {
752            OutgoingWireFormatPolicy::AlwaysPlaintext => {
753                let mut plaintext: PublicMessage = mls_auth_content.into();
754                // Set the membership tag only if the sender type is `Member`.
755                if plaintext.sender().is_member() {
756                    plaintext.set_membership_tag(
757                        provider.crypto(),
758                        self.ciphersuite(),
759                        self.message_secrets().membership_key(),
760                        self.message_secrets().serialized_context(),
761                    )?;
762                }
763                plaintext.into()
764            }
765            OutgoingWireFormatPolicy::AlwaysCiphertext => {
766                let ciphertext = self
767                    .encrypt(mls_auth_content, provider)
768                    // We can be sure the encryption will work because the plaintext was created by us
769                    .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
770                MlsMessageOut::from_private_message(ciphertext, self.version())
771            }
772        };
773        Ok(msg)
774    }
775
776    /// Check if the group is operational. Throws an error if the group is
777    /// inactive or if there is a pending commit.
778    fn is_operational(&self) -> Result<(), MlsGroupStateError> {
779        match self.group_state {
780            MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
781            MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
782            MlsGroupState::Operational => Ok(()),
783        }
784    }
785}
786
787// Methods used in tests
788impl MlsGroup {
789    #[cfg(any(feature = "test-utils", test))]
790    pub fn export_group_context(&self) -> &GroupContext {
791        self.context()
792    }
793
794    #[cfg(any(feature = "test-utils", test))]
795    pub fn tree_hash(&self) -> &[u8] {
796        self.public_group().group_context().tree_hash()
797    }
798
799    #[cfg(any(feature = "test-utils", test))]
800    pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
801        self.message_secrets_store.message_secrets_mut()
802    }
803
804    #[cfg(any(feature = "test-utils", test))]
805    pub fn print_ratchet_tree(&self, message: &str) {
806        println!("{}: {}", message, self.public_group().export_ratchet_tree());
807    }
808
809    #[cfg(any(feature = "test-utils", test))]
810    pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
811        self.public_group.context_mut()
812    }
813
814    #[cfg(test)]
815    pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
816        self.own_leaf_index = own_leaf_index;
817    }
818
819    #[cfg(test)]
820    pub(crate) fn own_tree_position(&self) -> TreePosition {
821        TreePosition::new(self.group_id().clone(), self.own_leaf_index())
822    }
823
824    #[cfg(test)]
825    pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
826        &self.message_secrets_store
827    }
828
829    #[cfg(test)]
830    pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
831        &self.resumption_psk_store
832    }
833
834    #[cfg(test)]
835    pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
836        self.public_group.set_group_context(group_context)
837    }
838
839    #[cfg(any(test, feature = "test-utils"))]
840    pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
841        let loaded = MlsGroup::load(storage, self.group_id())
842            .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
843        let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
844
845        if self != &other {
846            let mut diagnostics = Vec::new();
847
848            if self.mls_group_config != other.mls_group_config {
849                diagnostics.push(format!(
850                    "mls_group_config:\n  Current: {:?}\n  Loaded:  {:?}",
851                    self.mls_group_config, other.mls_group_config
852                ));
853            }
854            if self.public_group != other.public_group {
855                diagnostics.push(format!(
856                    "public_group:\n  Current: {:?}\n  Loaded:  {:?}",
857                    self.public_group, other.public_group
858                ));
859            }
860            if self.group_epoch_secrets != other.group_epoch_secrets {
861                diagnostics.push(format!(
862                    "group_epoch_secrets:\n  Current: {:?}\n  Loaded:  {:?}",
863                    self.group_epoch_secrets, other.group_epoch_secrets
864                ));
865            }
866            if self.own_leaf_index != other.own_leaf_index {
867                diagnostics.push(format!(
868                    "own_leaf_index:\n  Current: {:?}\n  Loaded:  {:?}",
869                    self.own_leaf_index, other.own_leaf_index
870                ));
871            }
872            if self.message_secrets_store != other.message_secrets_store {
873                diagnostics.push(format!(
874                    "message_secrets_store:\n  Current: {:?}\n  Loaded:  {:?}",
875                    self.message_secrets_store, other.message_secrets_store
876                ));
877            }
878            if self.resumption_psk_store != other.resumption_psk_store {
879                diagnostics.push(format!(
880                    "resumption_psk_store:\n  Current: {:?}\n  Loaded:  {:?}",
881                    self.resumption_psk_store, other.resumption_psk_store
882                ));
883            }
884            if self.own_leaf_nodes != other.own_leaf_nodes {
885                diagnostics.push(format!(
886                    "own_leaf_nodes:\n  Current: {:?}\n  Loaded:  {:?}",
887                    self.own_leaf_nodes, other.own_leaf_nodes
888                ));
889            }
890            if self.aad != other.aad {
891                diagnostics.push(format!(
892                    "aad:\n  Current: {:?}\n  Loaded:  {:?}",
893                    self.aad, other.aad
894                ));
895            }
896            if self.group_state != other.group_state {
897                diagnostics.push(format!(
898                    "group_state:\n  Current: {:?}\n  Loaded:  {:?}",
899                    self.group_state, other.group_state
900                ));
901            }
902            #[cfg(feature = "extensions-draft-08")]
903            if self.application_export_tree != other.application_export_tree {
904                diagnostics.push(format!(
905                    "application_export_tree:\n  Current: {:?}\n  Loaded:  {:?}",
906                    self.application_export_tree, other.application_export_tree
907                ));
908            }
909
910            log::error!(
911                "Loaded group does not match current group! Differing fields ({}):\n\n{}",
912                diagnostics.len(),
913                diagnostics.join("\n\n")
914            );
915
916            return Err(LibraryError::custom(
917                "Loaded group does not match current group",
918            ));
919        }
920
921        Ok(())
922    }
923}
924
925/// A [`StagedWelcome`] can be inspected and then turned into a [`MlsGroup`].
926/// This allows checking who authored the Welcome message.
927#[derive(Debug)]
928pub struct StagedWelcome {
929    // The group configuration. See [`MlsGroupJoinConfig`] for more information.
930    mls_group_config: MlsGroupJoinConfig,
931    public_group: PublicGroup,
932    group_epoch_secrets: GroupEpochSecrets,
933    own_leaf_index: LeafNodeIndex,
934
935    /// A [`MessageSecretsStore`] that stores message secrets.
936    /// By default this store has the length of 1, i.e. only the [`MessageSecrets`]
937    /// of the current epoch is kept.
938    /// If more secrets from past epochs should be kept in order to be
939    /// able to decrypt application messages from previous epochs, the size of
940    /// the store must be increased through [`max_past_epochs()`].
941    message_secrets_store: MessageSecretsStore,
942
943    /// A secret that is not stored as part of the [`MlsGroup`] after the group is created.
944    /// It can be used by the application to derive forward secure secrets.
945    #[cfg(feature = "extensions-draft-08")]
946    application_export_secret: ApplicationExportSecret,
947
948    /// Resumption psk store. This is where the resumption psks are kept in a rollover list.
949    resumption_psk_store: ResumptionPskStore,
950
951    /// The [`VerifiableGroupInfo`] from the [`Welcome`] message.
952    verifiable_group_info: VerifiableGroupInfo,
953
954    /// The key package bundle used for this welcome.
955    key_package_bundle: KeyPackageBundle,
956
957    /// If we got a path secret, these are the derived path keys.
958    path_keypairs: Option<Vec<EncryptionKeyPair>>,
959}
960
961/// A `Welcome` message that has been processed but not staged yet.
962///
963/// This may be used in order to retrieve information from the `Welcome` about
964/// the ratchet tree and PSKs.
965///
966/// Use `into_staged_welcome` to stage it into a [`StagedWelcome`].
967pub struct ProcessedWelcome {
968    // The group configuration. See [`MlsGroupJoinConfig`] for more information.
969    mls_group_config: MlsGroupJoinConfig,
970
971    // The following is the state after parsing the Welcome message, before actually
972    // building the group.
973    ciphersuite: Ciphersuite,
974    group_secrets: GroupSecrets,
975    key_schedule: crate::schedule::KeySchedule,
976    verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
977    resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
978    key_package_bundle: KeyPackageBundle,
979}