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#[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 pub fn with_group_id(mut self, group_id: GroupId) -> Self {
41 self.group_id = Some(group_id);
42 self
43 }
44
45 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 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 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 let mut resumption_psk_store = ResumptionPskStore::new(32);
108
109 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 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 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 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 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 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 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 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 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 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 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 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 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}