1use openmls_traits::{crypto::OpenMlsCrypto, 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#[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 #[cfg(feature = "virtual-clients-draft")]
40 vc_emulation_group_id: Option<GroupId>,
41}
42
43impl MlsGroupBuilder {
44 pub(super) fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn with_group_id(mut self, group_id: GroupId) -> Self {
50 self.group_id = Some(group_id);
51 self
52 }
53
54 #[cfg(feature = "virtual-clients-draft")]
71 pub fn vc_emulation(mut self, emulation_group_id: &GroupId) -> Self {
72 self.vc_emulation_group_id = Some(emulation_group_id.clone());
73 self
74 }
75
76 pub fn replace_old_group(mut self) -> Self {
78 self.replace_old_group = true;
79 self
80 }
81
82 pub fn build<Provider: OpenMlsProvider>(
84 self,
85 provider: &Provider,
86 signer: &impl Signer,
87 credential_with_key: CredentialWithKey,
88 ) -> Result<MlsGroup, NewGroupError<Provider::StorageError>> {
89 self.build_internal(provider, signer, credential_with_key, None)
90 }
91
92 pub(super) fn build_internal<Provider: OpenMlsProvider>(
101 self,
102 provider: &Provider,
103 signer: &impl Signer,
104 credential_with_key: CredentialWithKey,
105 mls_group_create_config_option: Option<MlsGroupCreateConfig>,
106 ) -> Result<MlsGroup, NewGroupError<Provider::StorageError>> {
107 let mls_group_create_config = mls_group_create_config_option
108 .unwrap_or_else(|| self.mls_group_create_config_builder.build());
109 let group_id = self
110 .group_id
111 .unwrap_or_else(|| GroupId::random(provider.rand()));
112 let ciphersuite = mls_group_create_config.ciphersuite;
113
114 provider
115 .crypto()
116 .supports(ciphersuite)
117 .map_err(|_| NewGroupError::UnsupportedCiphersuite(ciphersuite))?;
118
119 #[cfg(feature = "virtual-clients-draft")]
120 if let Some(emulation_group_id) = &self.vc_emulation_group_id {
121 let epoch_id =
122 crate::components::vc_derivation_info::require_newest_vc_derivation_epoch(
123 provider.storage(),
124 emulation_group_id,
125 )?;
126 return build_vc_internal(
127 provider,
128 signer,
129 credential_with_key,
130 mls_group_create_config,
131 group_id,
132 self.replace_old_group,
133 epoch_id,
134 );
135 }
136
137 if !self.replace_old_group
138 && MlsGroup::load(provider.storage(), &group_id)
139 .map_err(NewGroupError::StorageError)?
140 .is_some()
141 {
142 return Err(NewGroupError::GroupAlreadyExists);
143 }
144
145 let (public_group_builder, commit_secret, leaf_keypair) =
146 PublicGroup::builder(group_id, ciphersuite, credential_with_key)
147 .with_group_context_extensions(
148 mls_group_create_config.group_context_extensions.clone(),
149 )
150 .with_leaf_node_extensions(mls_group_create_config.leaf_node_extensions.clone())
151 .with_lifetime(*mls_group_create_config.lifetime())
152 .with_capabilities(mls_group_create_config.capabilities.clone())
153 .get_secrets(provider, signer)
154 .map_err(|e| match e {
155 PublicGroupBuildError::LibraryError(e) => NewGroupError::LibraryError(e),
156 PublicGroupBuildError::InvalidExtensions(e) => e.into(),
157 })?;
158
159 let serialized_group_context = public_group_builder
160 .group_context()
161 .tls_serialize_detached()
162 .map_err(LibraryError::missing_bound_check)?;
163
164 let joiner_secret = JoinerSecret::new(
168 provider.crypto(),
169 ciphersuite,
170 commit_secret,
171 &InitSecret::random(ciphersuite, provider.rand())
172 .map_err(LibraryError::unexpected_crypto_error)?,
173 &serialized_group_context,
174 )
175 .map_err(LibraryError::unexpected_crypto_error)?;
176
177 let mut resumption_psk_store = ResumptionPskStore::new(32);
179
180 let psk_secret = load_psks(provider.storage(), &resumption_psk_store, &self.psk_ids)
182 .and_then(|psks| PskSecret::new(provider.crypto(), ciphersuite, psks))
183 .map_err(|e| {
184 log::debug!("Unexpected PSK error: {e:?}");
185 LibraryError::custom("Unexpected PSK error")
186 })?;
187
188 let mut key_schedule =
189 KeySchedule::init(ciphersuite, provider.crypto(), &joiner_secret, psk_secret)?;
190 key_schedule
191 .add_context(provider.crypto(), &serialized_group_context)
192 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
193
194 let EpochSecretsResult {
195 epoch_secrets,
196 #[cfg(feature = "extensions-draft")]
197 application_exporter,
198 } = key_schedule
199 .epoch_secrets(provider.crypto(), ciphersuite)
200 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
201
202 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
203 serialized_group_context,
204 TreeSize::new(1),
205 LeafNodeIndex::new(0u32),
206 );
207
208 let initial_confirmation_tag = message_secrets
209 .confirmation_key()
210 .tag(provider.crypto(), ciphersuite, &[])
211 .map_err(LibraryError::unexpected_crypto_error)?;
212
213 let message_secrets_store = MessageSecretsStore::new_with_secret(
214 mls_group_create_config
215 .join_config
216 .past_epoch_deletion_policy(),
217 message_secrets,
218 );
219
220 let public_group = public_group_builder
221 .with_confirmation_tag(initial_confirmation_tag)
222 .build(provider.crypto())?;
223
224 let resumption_psk = group_epoch_secrets.resumption_psk();
226 resumption_psk_store.add(public_group.group_context().epoch(), resumption_psk.clone());
227
228 #[cfg(feature = "extensions-draft")]
229 #[cfg_attr(not(feature = "virtual-clients-draft"), allow(unused_mut))]
230 let mut application_export_tree = ApplicationExportTree::new(application_exporter);
231
232 #[cfg(feature = "virtual-clients-draft")]
234 if mls_group_create_config.emulation_group {
235 crate::components::vc_derivation_info::register_vc_derivation_epoch(
236 provider.crypto(),
237 provider.storage(),
238 Some(&mut application_export_tree),
239 crate::components::vc_derivation_info::VcDerivationEpochParams::for_public_group(
240 &public_group,
241 LeafNodeIndex::new(0),
242 ),
243 )?;
244 }
245
246 let mls_group = MlsGroup {
247 mls_group_config: mls_group_create_config.join_config.clone(),
248 own_leaf_nodes: vec![],
249 aad: vec![],
250 #[cfg(feature = "extensions-draft")]
251 safe_aad: crate::framing::SafeAad::empty(),
252 group_state: MlsGroupState::Operational,
253 public_group,
254 group_epoch_secrets,
255 own_leaf_index: LeafNodeIndex::new(0),
256 message_secrets_store,
257 resumption_psk_store,
258 #[cfg(feature = "extensions-draft")]
259 application_export_tree: Some(application_export_tree),
260 #[cfg(feature = "virtual-clients-draft")]
261 emulation_group: mls_group_create_config.emulation_group,
262 };
263
264 mls_group
265 .store(provider.storage())
266 .map_err(NewGroupError::StorageError)?;
267 mls_group
268 .store_epoch_keypairs(provider.storage(), &[leaf_keypair])
269 .map_err(NewGroupError::StorageError)?;
270
271 Ok(mls_group)
272 }
273
274 pub fn with_wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
278 self.mls_group_create_config_builder = self
279 .mls_group_create_config_builder
280 .wire_format_policy(wire_format_policy);
281 self
282 }
283
284 pub fn padding_size(mut self, padding_size: usize) -> Self {
286 self.mls_group_create_config_builder = self
287 .mls_group_create_config_builder
288 .padding_size(padding_size);
289 self
290 }
291
292 pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
310 self.mls_group_create_config_builder = self
311 .mls_group_create_config_builder
312 .max_past_epochs(max_past_epochs);
313 self
314 }
315
316 pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
330 self.mls_group_create_config_builder = self
331 .mls_group_create_config_builder
332 .set_past_epoch_deletion_policy(policy);
333 self
334 }
335
336 pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
338 self.mls_group_create_config_builder = self
339 .mls_group_create_config_builder
340 .number_of_resumption_psks(number_of_resumption_psks);
341 self
342 }
343
344 pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
346 self.mls_group_create_config_builder = self
347 .mls_group_create_config_builder
348 .use_ratchet_tree_extension(use_ratchet_tree_extension);
349 self
350 }
351
352 pub fn sender_ratchet_configuration(
355 mut self,
356 sender_ratchet_configuration: SenderRatchetConfiguration,
357 ) -> Self {
358 self.mls_group_create_config_builder = self
359 .mls_group_create_config_builder
360 .sender_ratchet_configuration(sender_ratchet_configuration);
361 self
362 }
363
364 pub fn lifetime(mut self, lifetime: Lifetime) -> Self {
366 self.mls_group_create_config_builder =
367 self.mls_group_create_config_builder.lifetime(lifetime);
368 self
369 }
370
371 pub fn ciphersuite(mut self, ciphersuite: Ciphersuite) -> Self {
373 self.mls_group_create_config_builder = self
374 .mls_group_create_config_builder
375 .ciphersuite(ciphersuite);
376 self
377 }
378
379 pub fn with_group_context_extensions(mut self, extensions: Extensions<GroupContext>) -> Self {
381 self.mls_group_create_config_builder = self
382 .mls_group_create_config_builder
383 .with_group_context_extensions(extensions);
384 self
385 }
386
387 pub fn with_leaf_node_extensions(
389 mut self,
390 extensions: Extensions<LeafNode>,
391 ) -> Result<Self, LeafNodeValidationError> {
392 self.mls_group_create_config_builder = self
393 .mls_group_create_config_builder
394 .with_leaf_node_extensions(extensions)?;
395 Ok(self)
396 }
397
398 pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self {
400 self.mls_group_create_config_builder = self
401 .mls_group_create_config_builder
402 .capabilities(capabilities);
403 self
404 }
405}
406
407#[cfg(feature = "virtual-clients-draft")]
422#[allow(clippy::too_many_arguments)]
423fn build_vc_internal<Provider: OpenMlsProvider>(
424 provider: &Provider,
425 signer: &impl Signer,
426 credential_with_key: CredentialWithKey,
427 mls_group_create_config: MlsGroupCreateConfig,
428 group_id: GroupId,
429 replace_old_group: bool,
430 epoch_id: crate::components::vc_derivation_info::EpochId,
431) -> Result<MlsGroup, NewGroupError<Provider::StorageError>> {
432 use openmls_traits::storage::StorageProvider as _;
433
434 use crate::{
435 components::vc_derivation_info::{
436 load_vc_epoch_state_and_tree, DerivationInfo, DerivationInfoTbe,
437 VirtualClientOperationType, VirtualClientsError,
438 },
439 schedule::EpochSecrets,
440 treesync::TreeSync,
441 };
442
443 if !replace_old_group
444 && MlsGroup::load(provider.storage(), &group_id)
445 .map_err(NewGroupError::StorageError)?
446 .is_some()
447 {
448 return Err(NewGroupError::GroupAlreadyExists);
449 }
450
451 let ciphersuite = mls_group_create_config.ciphersuite;
452 let capabilities = mls_group_create_config.capabilities.clone();
453
454 let resolved_dictionary = crate::components::vc_derivation_info::resolve_vc_leaf_dictionary(
458 Some(&capabilities),
459 Some(&mls_group_create_config.leaf_node_extensions),
460 None,
461 )?;
462
463 let (state, mut operation_tree) = load_vc_epoch_state_and_tree(provider, &epoch_id)?;
468 let (emulation_leaf_index, epoch_encryption_key, emulation_ciphersuite) = state.into_parts();
469 let (generation, operation_secret) = operation_tree.next_operation_secret(
470 provider.crypto(),
471 emulation_ciphersuite,
472 &epoch_id,
473 emulation_leaf_index,
474 VirtualClientOperationType::KeyPackage,
475 b"",
476 )?;
477 provider
478 .storage()
479 .write_vc_operation_tree(&epoch_id, &operation_tree)
480 .map_err(NewGroupError::StorageError)?;
481
482 let key_package_index = 0;
489 let key_package_seed = operation_secret.derive_key_package_seed_secret(
490 provider.crypto(),
491 ciphersuite,
492 key_package_index,
493 )?;
494 let leaf_encryption_keypair = key_package_seed
495 .derive_encryption_key_secret(provider.crypto(), ciphersuite)?
496 .generate_encryption_key_pair(provider.crypto(), ciphersuite)?;
497 let epoch_secret =
498 key_package_seed.derive_group_creation_secret(provider.crypto(), ciphersuite)?;
499
500 let leaf_encryption_key = leaf_encryption_keypair
503 .public_key()
504 .tls_serialize_detached()
505 .map_err(VirtualClientsError::from)?;
506 let tbe = DerivationInfoTbe::KeyPackage {
507 leaf_index: emulation_leaf_index,
508 generation,
509 key_package_index,
510 };
511 let derivation_info = DerivationInfo::encrypt(
512 provider.crypto(),
513 emulation_ciphersuite,
514 &epoch_encryption_key,
515 epoch_id.clone(),
516 &leaf_encryption_key,
517 &tbe,
518 )?;
519 let derivation_info_bytes = derivation_info
520 .tls_serialize_detached()
521 .map_err(VirtualClientsError::from)?;
522 let leaf_extensions = crate::components::vc_derivation_info::merge_vc_derivation_info(
523 Some(&mls_group_create_config.leaf_node_extensions),
524 resolved_dictionary,
525 derivation_info_bytes,
526 )?;
527
528 let (treesync, leaf_keypair) = TreeSync::new_vc(
530 provider,
531 signer,
532 ciphersuite,
533 credential_with_key,
534 *mls_group_create_config.lifetime(),
535 capabilities,
536 leaf_extensions,
537 leaf_encryption_keypair,
538 )?;
539 let group_context = GroupContext::create_initial_group_context(
540 ciphersuite,
541 group_id.clone(),
542 treesync.tree_hash().to_vec(),
543 mls_group_create_config.group_context_extensions.clone(),
544 );
545 let serialized_group_context = group_context
546 .tls_serialize_detached()
547 .map_err(LibraryError::missing_bound_check)?;
548
549 let epoch_secrets =
551 EpochSecrets::from_epoch_secret(provider.crypto(), ciphersuite, epoch_secret)
552 .map_err(LibraryError::unexpected_crypto_error)?;
553 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
554 serialized_group_context,
555 TreeSize::new(1),
556 LeafNodeIndex::new(0),
557 );
558 let initial_confirmation_tag = message_secrets
559 .confirmation_key()
560 .tag(provider.crypto(), ciphersuite, &[])
561 .map_err(LibraryError::unexpected_crypto_error)?;
562 let message_secrets_store = MessageSecretsStore::new_with_secret(
563 mls_group_create_config
564 .join_config
565 .past_epoch_deletion_policy(),
566 message_secrets,
567 );
568 let public_group = PublicGroup::new(
569 provider.crypto(),
570 treesync,
571 group_context,
572 initial_confirmation_tag,
573 )?;
574
575 let mut resumption_psk_store = ResumptionPskStore::new(32);
576 let resumption_psk = group_epoch_secrets.resumption_psk();
577 resumption_psk_store.add(public_group.group_context().epoch(), resumption_psk.clone());
578
579 let mls_group = MlsGroup {
580 mls_group_config: mls_group_create_config.join_config.clone(),
581 own_leaf_nodes: vec![],
582 aad: vec![],
583 safe_aad: crate::framing::SafeAad::empty(),
584 group_state: MlsGroupState::Operational,
585 public_group,
586 group_epoch_secrets,
587 own_leaf_index: LeafNodeIndex::new(0),
588 message_secrets_store,
589 resumption_psk_store,
590 application_export_tree: None,
593 emulation_group: false,
595 };
596
597 let mut bindings: crate::components::vc_derivation_info::VcEmulationBindings = provider
603 .storage()
604 .vc_emulation_bindings(&group_id)
605 .map_err(NewGroupError::StorageError)?
606 .unwrap_or_default();
607 let max_entries = mls_group.message_secrets_store.max_epochs.saturating_add(1);
608 bindings.insert(mls_group.epoch(), epoch_id, max_entries);
609 provider
610 .storage()
611 .write_vc_emulation_bindings(&group_id, &bindings)
612 .map_err(NewGroupError::StorageError)?;
613
614 mls_group
615 .store(provider.storage())
616 .map_err(NewGroupError::StorageError)?;
617 mls_group
618 .store_epoch_keypairs(provider.storage(), &[leaf_keypair])
619 .map_err(NewGroupError::StorageError)?;
620
621 Ok(mls_group)
622}