openmls/group/mls_group/
builder.rs

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