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