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")]
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")]
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")]
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            #[cfg(feature = "extensions-draft")]
187            safe_aad: crate::framing::SafeAad::empty(),
188            group_state: MlsGroupState::Operational,
189            public_group,
190            group_epoch_secrets,
191            own_leaf_index: LeafNodeIndex::new(0),
192            message_secrets_store,
193            resumption_psk_store,
194            #[cfg(feature = "extensions-draft")]
195            application_export_tree: Some(application_export_tree),
196        };
197
198        mls_group
199            .store(provider.storage())
200            .map_err(NewGroupError::StorageError)?;
201        mls_group
202            .store_epoch_keypairs(provider.storage(), &[leaf_keypair])
203            .map_err(NewGroupError::StorageError)?;
204
205        Ok(mls_group)
206    }
207
208    // MlsGroupCreateConfigBuilder options
209
210    /// Sets the `wire_format` property of the MlsGroup.
211    pub fn with_wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
212        self.mls_group_create_config_builder = self
213            .mls_group_create_config_builder
214            .wire_format_policy(wire_format_policy);
215        self
216    }
217
218    /// Sets the `padding_size` property of the MlsGroup.
219    pub fn padding_size(mut self, padding_size: usize) -> Self {
220        self.mls_group_create_config_builder = self
221            .mls_group_create_config_builder
222            .padding_size(padding_size);
223        self
224    }
225
226    /// Sets the `max_past_epochs` property of the MlsGroup.
227    /// This allows application messages from previous epochs to be decrypted.
228    ///
229    /// This method overrides the policy set by [`Self::set_past_epoch_deletion_policy()`],
230    /// and is equivalent to setting the past epoch deletion policy to
231    /// `PastEpochDeletionPolicy::MaxEpochs(max_past_epochs)`.
232    ///
233    /// **WARNING**
234    ///
235    ///
236    /// This feature enables the storage of message secrets from past epochs.
237    /// It is a trade-off between functionality and forward secrecy and should only be enabled
238    /// if the Delivery Service cannot guarantee that application messages will be sent in
239    /// the same epoch in which they were generated. The number for `max_epochs` should be
240    /// as low as possible.
241    ///
242    /// NOTE: This function will be deprecated in future releases.
243    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
244        self.mls_group_create_config_builder = self
245            .mls_group_create_config_builder
246            .max_past_epochs(max_past_epochs);
247        self
248    }
249
250    /// Set the policy for deleting past epoch secrets.
251    ///
252    /// By default, storage of past epoch secrets is disabled.
253    ///
254    /// This method overrides the configuration set by [`Self::max_past_epochs()`].
255    ///
256    /// **WARNING**
257    ///
258    /// This feature enables the storage of message secrets from past epochs.
259    /// It is a trade-off between functionality and forward secrecy and should only be enabled
260    /// if the Delivery Service cannot guarantee that application messages will be sent in
261    /// the same epoch in which they were generated. The number for `max_epochs` should be
262    /// as low as possible.
263    pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
264        self.mls_group_create_config_builder = self
265            .mls_group_create_config_builder
266            .set_past_epoch_deletion_policy(policy);
267        self
268    }
269
270    /// Sets the `number_of_resumption_psks` property of the MlsGroup.
271    pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
272        self.mls_group_create_config_builder = self
273            .mls_group_create_config_builder
274            .number_of_resumption_psks(number_of_resumption_psks);
275        self
276    }
277
278    /// Sets the `use_ratchet_tree_extension` property of the MlsGroup.
279    pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
280        self.mls_group_create_config_builder = self
281            .mls_group_create_config_builder
282            .use_ratchet_tree_extension(use_ratchet_tree_extension);
283        self
284    }
285
286    /// Sets the `sender_ratchet_configuration` property of the MlsGroup.
287    /// See [`SenderRatchetConfiguration`] for more information.
288    pub fn sender_ratchet_configuration(
289        mut self,
290        sender_ratchet_configuration: SenderRatchetConfiguration,
291    ) -> Self {
292        self.mls_group_create_config_builder = self
293            .mls_group_create_config_builder
294            .sender_ratchet_configuration(sender_ratchet_configuration);
295        self
296    }
297
298    /// Sets the `lifetime` of the group creator's leaf.
299    pub fn lifetime(mut self, lifetime: Lifetime) -> Self {
300        self.mls_group_create_config_builder =
301            self.mls_group_create_config_builder.lifetime(lifetime);
302        self
303    }
304
305    /// Sets the `ciphersuite` of the MlsGroup.
306    pub fn ciphersuite(mut self, ciphersuite: Ciphersuite) -> Self {
307        self.mls_group_create_config_builder = self
308            .mls_group_create_config_builder
309            .ciphersuite(ciphersuite);
310        self
311    }
312
313    /// Sets the initial group context extensions
314    pub fn with_group_context_extensions(mut self, extensions: Extensions<GroupContext>) -> Self {
315        self.mls_group_create_config_builder = self
316            .mls_group_create_config_builder
317            .with_group_context_extensions(extensions);
318        self
319    }
320
321    /// Sets the initial leaf node extensions
322    pub fn with_leaf_node_extensions(
323        mut self,
324        extensions: Extensions<LeafNode>,
325    ) -> Result<Self, LeafNodeValidationError> {
326        self.mls_group_create_config_builder = self
327            .mls_group_create_config_builder
328            .with_leaf_node_extensions(extensions)?;
329        Ok(self)
330    }
331
332    /// Sets the group creator's [`Capabilities`]
333    pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self {
334        self.mls_group_create_config_builder = self
335            .mls_group_create_config_builder
336            .capabilities(capabilities);
337        self
338    }
339}