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