Skip to main content

openmls/group/mls_group/
builder.rs

1use openmls_traits::{signatures::Signer, types::Ciphersuite};
2use tls_codec::Serialize;
3
4#[cfg(feature = "extensions-draft-08")]
5use crate::schedule::application_export_tree::ApplicationExportTree;
6use crate::{
7    binary_tree::{array_representation::TreeSize, LeafNodeIndex},
8    credentials::CredentialWithKey,
9    error::LibraryError,
10    extensions::Extensions,
11    group::{
12        config::PastEpochDeletionPolicy, past_secrets::MessageSecretsStore,
13        public_group::errors::PublicGroupBuildError, GroupContext, GroupId, MlsGroup,
14        MlsGroupCreateConfig, MlsGroupCreateConfigBuilder, MlsGroupState, NewGroupError,
15        PublicGroup, WireFormatPolicy,
16    },
17    key_packages::Lifetime,
18    schedule::{
19        psk::{load_psks, store::ResumptionPskStore, PskSecret},
20        EpochSecretsResult, InitSecret, JoinerSecret, KeySchedule, PreSharedKeyId,
21    },
22    storage::OpenMlsProvider,
23    tree::sender_ratchet::SenderRatchetConfiguration,
24    treesync::{
25        errors::LeafNodeValidationError,
26        node::leaf_node::{Capabilities, LeafNode},
27    },
28};
29
30/// Builder struct for an [`MlsGroup`].
31#[derive(Default, Debug)]
32pub struct MlsGroupBuilder {
33    group_id: Option<GroupId>,
34    mls_group_create_config_builder: MlsGroupCreateConfigBuilder,
35    replace_old_group: bool,
36    psk_ids: Vec<PreSharedKeyId>,
37}
38
39impl MlsGroupBuilder {
40    pub(super) fn new() -> Self {
41        Self::default()
42    }
43
44    /// Sets the group ID of the [`MlsGroup`].
45    pub fn with_group_id(mut self, group_id: GroupId) -> Self {
46        self.group_id = Some(group_id);
47        self
48    }
49
50    /// Instruct the builder to replace any existing group with the same ID.
51    pub fn replace_old_group(mut self) -> Self {
52        self.replace_old_group = true;
53        self
54    }
55
56    /// Build a new group as configured by this builder.
57    pub fn build<Provider: OpenMlsProvider>(
58        self,
59        provider: &Provider,
60        signer: &impl Signer,
61        credential_with_key: CredentialWithKey,
62    ) -> Result<MlsGroup, NewGroupError<Provider::StorageError>> {
63        self.build_internal(provider, signer, credential_with_key, None)
64    }
65
66    /// Build a new group with the given group ID.
67    ///
68    /// If an [`MlsGroupCreateConfig`] is provided, it will be used to configure the
69    /// group. Otherwise, the internal builder is used to build one with the
70    /// parameters set on this builder.
71    ///
72    /// If a group with the same ID already exists in storage and
73    /// `replace_old_group` was not set, an error will be returned.
74    pub(super) fn build_internal<Provider: OpenMlsProvider>(
75        self,
76        provider: &Provider,
77        signer: &impl Signer,
78        credential_with_key: CredentialWithKey,
79        mls_group_create_config_option: Option<MlsGroupCreateConfig>,
80    ) -> Result<MlsGroup, NewGroupError<Provider::StorageError>> {
81        let mls_group_create_config = mls_group_create_config_option
82            .unwrap_or_else(|| self.mls_group_create_config_builder.build());
83        let group_id = self
84            .group_id
85            .unwrap_or_else(|| GroupId::random(provider.rand()));
86        let ciphersuite = mls_group_create_config.ciphersuite;
87
88        if !self.replace_old_group
89            && MlsGroup::load(provider.storage(), &group_id)
90                .map_err(NewGroupError::StorageError)?
91                .is_some()
92        {
93            return Err(NewGroupError::GroupAlreadyExists);
94        }
95
96        let (public_group_builder, commit_secret, leaf_keypair) =
97            PublicGroup::builder(group_id, ciphersuite, credential_with_key)
98                .with_group_context_extensions(
99                    mls_group_create_config.group_context_extensions.clone(),
100                )
101                .with_leaf_node_extensions(mls_group_create_config.leaf_node_extensions.clone())
102                .with_lifetime(*mls_group_create_config.lifetime())
103                .with_capabilities(mls_group_create_config.capabilities.clone())
104                .get_secrets(provider, signer)
105                .map_err(|e| match e {
106                    PublicGroupBuildError::LibraryError(e) => NewGroupError::LibraryError(e),
107                    PublicGroupBuildError::InvalidExtensions(e) => e.into(),
108                })?;
109
110        let serialized_group_context = public_group_builder
111            .group_context()
112            .tls_serialize_detached()
113            .map_err(LibraryError::missing_bound_check)?;
114
115        // Derive an initial joiner secret based on the commit secret.
116        // Derive an epoch secret from the joiner secret.
117        // We use a random `InitSecret` for initialization.
118        let joiner_secret = JoinerSecret::new(
119            provider.crypto(),
120            ciphersuite,
121            commit_secret,
122            &InitSecret::random(ciphersuite, provider.rand())
123                .map_err(LibraryError::unexpected_crypto_error)?,
124            &serialized_group_context,
125        )
126        .map_err(LibraryError::unexpected_crypto_error)?;
127
128        // TODO(#1357)
129        let mut resumption_psk_store = ResumptionPskStore::new(32);
130
131        // Prepare the PskSecret
132        let psk_secret = load_psks(provider.storage(), &resumption_psk_store, &self.psk_ids)
133            .and_then(|psks| PskSecret::new(provider.crypto(), ciphersuite, psks))
134            .map_err(|e| {
135                log::debug!("Unexpected PSK error: {e:?}");
136                LibraryError::custom("Unexpected PSK error")
137            })?;
138
139        let mut key_schedule =
140            KeySchedule::init(ciphersuite, provider.crypto(), &joiner_secret, psk_secret)?;
141        key_schedule
142            .add_context(provider.crypto(), &serialized_group_context)
143            .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
144
145        let EpochSecretsResult {
146            epoch_secrets,
147            #[cfg(feature = "extensions-draft-08")]
148            application_exporter,
149        } = key_schedule
150            .epoch_secrets(provider.crypto(), ciphersuite)
151            .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
152
153        let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
154            serialized_group_context,
155            TreeSize::new(1),
156            LeafNodeIndex::new(0u32),
157        );
158
159        let initial_confirmation_tag = message_secrets
160            .confirmation_key()
161            .tag(provider.crypto(), ciphersuite, &[])
162            .map_err(LibraryError::unexpected_crypto_error)?;
163
164        let message_secrets_store = MessageSecretsStore::new_with_secret(
165            mls_group_create_config
166                .join_config
167                .past_epoch_deletion_policy(),
168            message_secrets,
169        );
170
171        let public_group = public_group_builder
172            .with_confirmation_tag(initial_confirmation_tag)
173            .build(provider.crypto())?;
174
175        // We already add a resumption PSK for epoch 0 to make things more unified.
176        let resumption_psk = group_epoch_secrets.resumption_psk();
177        resumption_psk_store.add(public_group.group_context().epoch(), resumption_psk.clone());
178
179        #[cfg(feature = "extensions-draft-08")]
180        let application_export_tree = ApplicationExportTree::new(application_exporter);
181
182        let mls_group = MlsGroup {
183            mls_group_config: mls_group_create_config.join_config.clone(),
184            own_leaf_nodes: vec![],
185            aad: vec![],
186            group_state: MlsGroupState::Operational,
187            public_group,
188            group_epoch_secrets,
189            own_leaf_index: LeafNodeIndex::new(0),
190            message_secrets_store,
191            resumption_psk_store,
192            #[cfg(feature = "extensions-draft-08")]
193            application_export_tree: Some(application_export_tree),
194        };
195
196        mls_group
197            .store(provider.storage())
198            .map_err(NewGroupError::StorageError)?;
199        mls_group
200            .store_epoch_keypairs(provider.storage(), &[leaf_keypair])
201            .map_err(NewGroupError::StorageError)?;
202
203        Ok(mls_group)
204    }
205
206    // MlsGroupCreateConfigBuilder options
207
208    /// Sets the `wire_format` property of the MlsGroup.
209    pub fn with_wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
210        self.mls_group_create_config_builder = self
211            .mls_group_create_config_builder
212            .wire_format_policy(wire_format_policy);
213        self
214    }
215
216    /// Sets the `padding_size` property of the MlsGroup.
217    pub fn padding_size(mut self, padding_size: usize) -> Self {
218        self.mls_group_create_config_builder = self
219            .mls_group_create_config_builder
220            .padding_size(padding_size);
221        self
222    }
223
224    /// Sets the `max_past_epochs` property of the MlsGroup.
225    /// This allows application messages from previous epochs to be decrypted.
226    ///
227    /// This method overrides the policy set by [`Self::set_past_epoch_deletion_policy()`],
228    /// and is equivalent to setting the past epoch deletion policy to
229    /// `PastEpochDeletionPolicy::MaxEpochs(max_past_epochs)`.
230    ///
231    /// **WARNING**
232    ///
233    ///
234    /// This feature enables the storage of message secrets from past epochs.
235    /// It is a trade-off between functionality and forward secrecy and should only be enabled
236    /// if the Delivery Service cannot guarantee that application messages will be sent in
237    /// the same epoch in which they were generated. The number for `max_epochs` should be
238    /// as low as possible.
239    ///
240    /// NOTE: This function will be deprecated in future releases.
241    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
242        self.mls_group_create_config_builder = self
243            .mls_group_create_config_builder
244            .max_past_epochs(max_past_epochs);
245        self
246    }
247
248    /// Set the policy for deleting past epoch secrets.
249    ///
250    /// By default, storage of past epoch secrets is disabled.
251    ///
252    /// This method overrides the configuration set by [`Self::max_past_epochs()`].
253    ///
254    /// **WARNING**
255    ///
256    /// This feature enables the storage of message secrets from past epochs.
257    /// It is a trade-off between functionality and forward secrecy and should only be enabled
258    /// if the Delivery Service cannot guarantee that application messages will be sent in
259    /// the same epoch in which they were generated. The number for `max_epochs` should be
260    /// as low as possible.
261    pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
262        self.mls_group_create_config_builder = self
263            .mls_group_create_config_builder
264            .set_past_epoch_deletion_policy(policy);
265        self
266    }
267
268    /// Sets the `number_of_resumption_psks` property of the MlsGroup.
269    pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
270        self.mls_group_create_config_builder = self
271            .mls_group_create_config_builder
272            .number_of_resumption_psks(number_of_resumption_psks);
273        self
274    }
275
276    /// Sets the `use_ratchet_tree_extension` property of the MlsGroup.
277    pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
278        self.mls_group_create_config_builder = self
279            .mls_group_create_config_builder
280            .use_ratchet_tree_extension(use_ratchet_tree_extension);
281        self
282    }
283
284    /// Sets the `sender_ratchet_configuration` property of the MlsGroup.
285    /// See [`SenderRatchetConfiguration`] for more information.
286    pub fn sender_ratchet_configuration(
287        mut self,
288        sender_ratchet_configuration: SenderRatchetConfiguration,
289    ) -> Self {
290        self.mls_group_create_config_builder = self
291            .mls_group_create_config_builder
292            .sender_ratchet_configuration(sender_ratchet_configuration);
293        self
294    }
295
296    /// Sets the `lifetime` of the group creator's leaf.
297    pub fn lifetime(mut self, lifetime: Lifetime) -> Self {
298        self.mls_group_create_config_builder =
299            self.mls_group_create_config_builder.lifetime(lifetime);
300        self
301    }
302
303    /// Sets the `ciphersuite` of the MlsGroup.
304    pub fn ciphersuite(mut self, ciphersuite: Ciphersuite) -> Self {
305        self.mls_group_create_config_builder = self
306            .mls_group_create_config_builder
307            .ciphersuite(ciphersuite);
308        self
309    }
310
311    /// Sets the initial group context extensions
312    pub fn with_group_context_extensions(mut self, extensions: Extensions<GroupContext>) -> Self {
313        self.mls_group_create_config_builder = self
314            .mls_group_create_config_builder
315            .with_group_context_extensions(extensions);
316        self
317    }
318
319    /// Sets the initial leaf node extensions
320    pub fn with_leaf_node_extensions(
321        mut self,
322        extensions: Extensions<LeafNode>,
323    ) -> Result<Self, LeafNodeValidationError> {
324        self.mls_group_create_config_builder = self
325            .mls_group_create_config_builder
326            .with_leaf_node_extensions(extensions)?;
327        Ok(self)
328    }
329
330    /// Sets the group creator's [`Capabilities`]
331    pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self {
332        self.mls_group_create_config_builder = self
333            .mls_group_create_config_builder
334            .capabilities(capabilities);
335        self
336    }
337}