1use errors::NewGroupError;
2use openmls_traits::{crypto::OpenMlsCrypto, storage::StorageProvider as StorageProviderTrait};
3
4use super::{builder::MlsGroupBuilder, *};
5use crate::{
6 credentials::CredentialWithKey,
7 extensions::Extensions,
8 group::{
9 commit_builder::external_commits::ExternalCommitBuilder,
10 errors::{ExportSecretError, ExternalCommitError, WelcomeError},
11 },
12 key_packages::KeyPackage,
13 messages::{
14 group_info::{GroupInfo, VerifiableGroupInfo},
15 Welcome,
16 },
17 schedule::{
18 psk::{store::ResumptionPskStore, PreSharedKeyId, Psk, ResumptionPskUsage},
19 EpochSecretsResult,
20 },
21 storage::OpenMlsProvider,
22 treesync::{
23 errors::{DerivePathError, PublicTreeError},
24 node::leaf_node::{Capabilities, LeafNodeParameters},
25 RatchetTreeIn,
26 },
27};
28
29#[cfg(feature = "virtual-clients-draft")]
30use crate::{
31 ciphersuite::hash_ref::KeyPackageRef,
32 component::ComponentId,
33 components::vc_derivation_info::{
34 load_vc_epoch_state_and_tree, register_vc_derivation_epoch,
35 write_vc_emulation_binding_with_pruning, DerivationInfoTbe, EpochId,
36 RetainedKeyPackageMaterial, VcDerivationEpochParams, VcDerivationEpochState,
37 VcWelcomeMaterial, VirtualClientOperationType, VirtualClientsError,
38 },
39 framing::{mls_auth_content::AuthenticatedContent, ProtocolMessage, SafeAad, Sender},
40 group::{
41 config::PastEpochDeletionPolicy,
42 errors::{
43 ProcessMessageError, StageCommitError, VcExternalCommitJoinError,
44 VcGroupCreationJoinError,
45 },
46 mls_group::processing::{
47 committed_app_data_update_proposals, AppDataDictionaryUpdater, AppDataUpdates,
48 },
49 public_group::{errors::ApplyAppDataUpdateError, PublicGroup},
50 },
51 messages::proposals::{AppDataUpdateProposal, AppEphemeralProposal, Proposal, ProposalOrRef},
52 prelude::mls_content::FramedContentBody,
53 schedule::{EpochSecrets, InitSecret},
54 treesync::node::leaf_node::LeafNodeSource,
55};
56
57impl MlsGroup {
58 pub fn builder() -> MlsGroupBuilder {
63 MlsGroupBuilder::new()
64 }
65
66 pub fn new<Provider: OpenMlsProvider>(
69 provider: &Provider,
70 signer: &impl Signer,
71 mls_group_create_config: &MlsGroupCreateConfig,
72 credential_with_key: CredentialWithKey,
73 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
74 MlsGroupBuilder::new().build_internal(
75 provider,
76 signer,
77 credential_with_key,
78 Some(mls_group_create_config.clone()),
79 )
80 }
81
82 pub fn new_with_group_id<Provider: OpenMlsProvider>(
85 provider: &Provider,
86 signer: &impl Signer,
87 mls_group_create_config: &MlsGroupCreateConfig,
88 group_id: GroupId,
89 credential_with_key: CredentialWithKey,
90 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
91 MlsGroupBuilder::new()
92 .with_group_id(group_id)
93 .build_internal(
94 provider,
95 signer,
96 credential_with_key,
97 Some(mls_group_create_config.clone()),
98 )
99 }
100
101 #[allow(clippy::too_many_arguments)]
116 #[deprecated(
117 since = "0.7.1",
118 note = "Use the `MlsGroup::external_commit_builder` instead."
119 )]
120 pub fn join_by_external_commit<Provider: OpenMlsProvider>(
121 provider: &Provider,
122 signer: &impl Signer,
123 ratchet_tree: Option<RatchetTreeIn>,
124 verifiable_group_info: VerifiableGroupInfo,
125 mls_group_config: &MlsGroupJoinConfig,
126 capabilities: Option<Capabilities>,
127 extensions: Option<Extensions<LeafNode>>,
128 aad: &[u8],
129 credential_with_key: CredentialWithKey,
130 ) -> Result<(Self, MlsMessageOut, Option<GroupInfo>), ExternalCommitError<Provider::StorageError>>
131 {
132 let leaf_node_parameters = LeafNodeParameters::builder()
133 .with_capabilities(capabilities.unwrap_or_default())
134 .with_extensions(extensions.unwrap_or_default())
135 .build();
136
137 let mut external_commit_builder = ExternalCommitBuilder::new()
138 .with_aad(aad.to_vec())
139 .with_config(mls_group_config.clone());
140
141 if let Some(ratchet_tree) = ratchet_tree {
142 external_commit_builder = external_commit_builder.with_ratchet_tree(ratchet_tree)
143 }
144
145 let (mls_group, commit_message_bundle) = external_commit_builder
146 .build_group(provider, verifiable_group_info, credential_with_key)?
147 .leaf_node_parameters(leaf_node_parameters)
148 .load_psks(provider.storage())
149 .map_err(|e| {
150 log::error!("Error loading PSKs for external commit: {e:?}");
151 LibraryError::custom("Error loading PSKs for external commit")
152 })?
153 .build(provider.rand(), provider.crypto(), signer, |_| true)?
154 .finalize(provider)?;
155
156 let (commit, _, group_info) = commit_message_bundle.into_contents();
157
158 Ok((mls_group, commit, group_info))
159 }
160}
161
162impl ProcessedWelcome {
163 pub fn new_from_welcome<Provider: OpenMlsProvider>(
170 provider: &Provider,
171 mls_group_config: &MlsGroupJoinConfig,
172 welcome: Welcome,
173 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
174 Self::new_from_welcome_inner(provider, mls_group_config, welcome, None)
175 }
176
177 pub(crate) fn new_from_welcome_inner<Provider: OpenMlsProvider>(
185 provider: &Provider,
186 mls_group_config: &MlsGroupJoinConfig,
187 welcome: Welcome,
188 branch_info: Option<&BranchInfo>,
189 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
190 let (resumption_psk_store, key_material, group_secrets) =
191 decrypt_group_secrets(provider, mls_group_config, &welcome)?;
192
193 finish_processed_welcome(
194 provider,
195 mls_group_config,
196 welcome.ciphersuite(),
197 resumption_psk_store,
198 key_material,
199 group_secrets,
200 &welcome,
201 branch_info,
202 )
203 }
204
205 pub fn unverified_group_info(&self) -> &VerifiableGroupInfo {
209 &self.verifiable_group_info
210 }
211
212 pub fn psks(&self) -> &[PreSharedKeyId] {
216 &self.group_secrets.psks
217 }
218
219 pub fn into_staged_welcome<Provider: OpenMlsProvider>(
222 self,
223 provider: &Provider,
224 ratchet_tree: Option<RatchetTreeIn>,
225 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
226 self.into_staged_welcome_inner(
227 provider,
228 ratchet_tree,
229 LeafNodeLifetimePolicy::Verify,
230 false,
231 )
232 }
233
234 pub(crate) fn into_staged_welcome_inner<Provider: OpenMlsProvider>(
237 mut self,
238 provider: &Provider,
239 ratchet_tree: Option<RatchetTreeIn>,
240 validate_lifetimes: LeafNodeLifetimePolicy,
241 replace_old_group: bool,
242 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
243 if !replace_old_group
245 && MlsGroup::load(provider.storage(), self.verifiable_group_info.group_id())
246 .map_err(WelcomeError::StorageError)?
247 .is_some()
248 {
249 return Err(WelcomeError::GroupAlreadyExists);
250 }
251
252 let ratchet_tree = match self.verifiable_group_info.extensions().ratchet_tree() {
259 Some(extension) => extension.ratchet_tree().clone(),
260 None => match ratchet_tree {
261 Some(ratchet_tree) => ratchet_tree,
262 None => return Err(WelcomeError::MissingRatchetTree),
263 },
264 };
265
266 let (public_group, _group_info_extensions) = PublicGroup::from_ratchet_tree(
269 provider.crypto(),
270 ratchet_tree,
271 self.verifiable_group_info.clone(),
272 ProposalStore::new(),
273 validate_lifetimes,
274 )?;
275
276 let own_leaf_index = match &self.key_material.inner() {
282 WelcomeKeyMaterialInner::KeyPackage(key_package_bundle) => {
283 let added_leaf_supports_all_group_context_extensions = public_group
287 .group_context()
288 .extensions()
289 .iter()
290 .all(|extension| {
291 key_package_bundle
292 .key_package
293 .leaf_node()
294 .supports_extension(&extension.extension_type())
295 });
296 if !added_leaf_supports_all_group_context_extensions {
297 return Err(WelcomeError::UnsupportedExtensions);
298 }
299
300 public_group
301 .members()
302 .find_map(|m| {
303 if m.signature_key
304 == key_package_bundle
305 .key_package()
306 .leaf_node()
307 .signature_key()
308 .as_slice()
309 {
310 Some(m.index)
311 } else {
312 None
313 }
314 })
315 .ok_or(WelcomeError::PublicTreeError(
316 PublicTreeError::MalformedTree,
317 ))?
318 }
319 #[cfg(feature = "virtual-clients-draft")]
320 WelcomeKeyMaterialInner::VirtualClient(material) => {
321 find_and_validate_vc_own_leaf(provider, &public_group, material)?
322 }
323 };
324
325 struct KeyScheduleResult {
326 group_epoch_secrets: GroupEpochSecrets,
327 message_secrets: MessageSecrets,
328 #[cfg(feature = "extensions-draft")]
329 application_exporter: ApplicationExportSecret,
330 }
331 let KeyScheduleResult {
332 group_epoch_secrets,
333 message_secrets,
334 #[cfg(feature = "extensions-draft")]
335 application_exporter: application_export_secret,
336 } = {
337 let serialized_group_context = public_group
338 .group_context()
339 .tls_serialize_detached()
340 .map_err(LibraryError::missing_bound_check)?;
341
342 let EpochSecretsResult {
343 epoch_secrets,
344 #[cfg(feature = "extensions-draft")]
345 application_exporter,
346 } = self.epoch_secrets;
347
348 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
349 serialized_group_context,
350 public_group.tree_size(),
351 own_leaf_index,
352 );
353
354 KeyScheduleResult {
355 group_epoch_secrets,
356 message_secrets,
357 #[cfg(feature = "extensions-draft")]
358 application_exporter,
359 }
360 };
361
362 let confirmation_tag = message_secrets
363 .confirmation_key()
364 .tag(
365 provider.crypto(),
366 self.ciphersuite,
367 public_group.group_context().confirmed_transcript_hash(),
368 )
369 .map_err(LibraryError::unexpected_crypto_error)?;
370
371 if &confirmation_tag != public_group.confirmation_tag() {
374 log::error!("Confirmation tag mismatch");
375 log_crypto!(trace, " Got: {:x?}", confirmation_tag);
376 log_crypto!(trace, " Expected: {:x?}", public_group.confirmation_tag());
377
378 if !crate::skip_validation::is_disabled::confirmation_tag() {
381 return Err(WelcomeError::ConfirmationTagMismatch);
382 }
383 }
384
385 let message_secrets_store = MessageSecretsStore::new_with_secret(
386 &PastEpochDeletionPolicy::MaxEpochs(0),
387 message_secrets,
388 );
389
390 let resumption_psk = group_epoch_secrets.resumption_psk();
392 self.resumption_psk_store
393 .add(public_group.group_context().epoch(), resumption_psk.clone());
394
395 let welcome_sender_index = self.verifiable_group_info.signer();
396 let path_keypairs = if let Some(path_secret) = self.group_secrets.path_secret {
397 let (path_keypairs, _commit_secret) = public_group
398 .derive_path_secrets(
399 provider.crypto(),
400 self.ciphersuite,
401 path_secret,
402 welcome_sender_index,
403 own_leaf_index,
404 )
405 .map_err(|e| match e {
406 DerivePathError::LibraryError(e) => e.into(),
407 DerivePathError::PublicKeyMismatch => {
408 WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch)
409 }
410 })?;
411 Some(path_keypairs)
412 } else {
413 None
414 };
415
416 let staged_welcome = StagedWelcome {
417 mls_group_config: self.mls_group_config,
418 public_group,
419 group_epoch_secrets,
420 own_leaf_index,
421 message_secrets_store,
422 #[cfg(feature = "extensions-draft")]
423 application_export_secret,
424 resumption_psk_store: self.resumption_psk_store,
425 verifiable_group_info: self.verifiable_group_info,
426 key_material: self.key_material,
427 path_keypairs,
428 #[cfg(feature = "virtual-clients-draft")]
429 emulation_group: false,
430 };
431
432 Ok(staged_welcome)
433 }
434
435 pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
440 &self,
441 crypto: &CryptoProvider,
442 label: &str,
443 context: &[u8],
444 key_length: usize,
445 ) -> Result<ExportedSecret<ProcessedWelcomeExport>, ExportSecretError> {
446 if key_length > u16::MAX as usize {
447 log::error!("Got a key that is larger than u16::MAX");
448 return Err(ExportSecretError::KeyLengthTooLong);
449 }
450
451 Ok(ExportedSecret::new(
452 self.epoch_secrets
453 .epoch_secrets
454 .exporter_secret()
455 .derive_exported_secret(self.ciphersuite, crypto, label, context, key_length)
456 .map_err(LibraryError::unexpected_crypto_error)?,
457 ))
458 }
459
460 pub fn own_key_package(&self) -> Option<&KeyPackage> {
463 self.key_material
464 .key_package_bundle()
465 .map(|bundle| bundle.key_package())
466 }
467}
468
469impl StagedWelcome {
470 pub fn new_from_welcome<Provider: OpenMlsProvider>(
478 provider: &Provider,
479 mls_group_config: &MlsGroupJoinConfig,
480 welcome: Welcome,
481 ratchet_tree: Option<RatchetTreeIn>,
482 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
483 let processed_welcome =
484 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
485
486 processed_welcome.into_staged_welcome(provider, ratchet_tree)
487 }
488
489 pub fn build_from_welcome<'a, Provider: OpenMlsProvider>(
494 provider: &'a Provider,
495 mls_group_config: &MlsGroupJoinConfig,
496 welcome: Welcome,
497 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
498 let processed_welcome =
499 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
500
501 Ok(JoinBuilder::new(provider, processed_welcome))
503 }
504
505 pub fn build_from_branch<'a, Provider: OpenMlsProvider>(
544 provider: &'a Provider,
545 mls_group_config: &MlsGroupJoinConfig,
546 welcome: Welcome,
547 branch_info: BranchInfo,
548 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
549 Self::process_branch_welcome(provider, mls_group_config, welcome)?
550 .build_from_branch(provider, branch_info)
551 }
552
553 pub fn process_branch_welcome<Provider: OpenMlsProvider>(
572 provider: &Provider,
573 mls_group_config: &MlsGroupJoinConfig,
574 welcome: Welcome,
575 ) -> Result<PendingBranchWelcome, WelcomeError<Provider::StorageError>> {
576 let (resumption_psk_store, key_material, group_secrets) =
577 decrypt_group_secrets(provider, mls_group_config, &welcome)?;
578
579 Ok(PendingBranchWelcome {
580 mls_group_config: mls_group_config.clone(),
581 ciphersuite: welcome.ciphersuite(),
582 welcome,
583 resumption_psk_store,
584 key_material,
585 group_secrets,
586 })
587 }
588
589 pub fn welcome_sender_index(&self) -> LeafNodeIndex {
593 self.verifiable_group_info.signer()
594 }
595
596 pub fn welcome_sender(&self) -> Result<&LeafNode, LibraryError> {
600 let sender_index = self.welcome_sender_index();
601 self.public_group
602 .leaf(sender_index)
603 .ok_or_else(|| LibraryError::custom("no leaf with given welcome sender index exists"))
604 }
605
606 pub fn own_leaf_index(&self) -> LeafNodeIndex {
608 self.own_leaf_index
609 }
610
611 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
613 self.public_group.leaf(self.own_leaf_index())
614 }
615
616 pub fn group_context(&self) -> &GroupContext {
618 self.public_group.group_context()
619 }
620
621 pub fn members(&self) -> impl Iterator<Item = Member> + '_ {
623 self.public_group.members()
624 }
625
626 #[cfg(feature = "extensions-draft")]
628 pub fn application_export_secret(&self) -> &ApplicationExportSecret {
629 &self.application_export_secret
630 }
631
632 #[cfg(feature = "virtual-clients-draft")]
642 pub fn emulation_group(mut self, emulation_group: bool) -> Self {
643 self.emulation_group = emulation_group;
644 self
645 }
646
647 pub fn into_group<Provider: OpenMlsProvider>(
649 self,
650 provider: &Provider,
651 ) -> Result<MlsGroup, WelcomeError<Provider::StorageError>> {
652 let group_keypairs = if let Some(path_keypairs) = self.path_keypairs {
655 let mut keypairs = vec![self.key_material.encryption_key_pair()];
656 keypairs.extend_from_slice(&path_keypairs);
657 keypairs
658 } else {
659 vec![self.key_material.encryption_key_pair()]
660 };
661
662 #[cfg(feature = "extensions-draft")]
663 #[cfg_attr(not(feature = "virtual-clients-draft"), allow(unused_mut))]
664 let mut application_export_tree =
665 ApplicationExportTree::new(self.application_export_secret);
666
667 #[cfg(feature = "virtual-clients-draft")]
670 if self.emulation_group {
671 register_vc_derivation_epoch(
672 provider.crypto(),
673 provider.storage(),
674 Some(&mut application_export_tree),
675 VcDerivationEpochParams::for_public_group(
676 &self.public_group,
677 self.own_leaf_index,
678 self.mls_group_config
679 .vc_derivation_epoch_retention_policy()
680 .clone(),
681 ),
682 )?;
683 }
684
685 #[cfg(feature = "virtual-clients-draft")]
691 if let Some(derivation_info) = self
692 .public_group
693 .leaf(self.own_leaf_index)
694 .map(LeafNode::vc_derivation_info)
695 .transpose()?
696 .flatten()
697 {
698 let epoch_id = derivation_info.epoch_id().clone();
699 let group_id = self.public_group.group_id();
700 provider
701 .storage()
702 .vc_derivation_epoch_state::<_, VcDerivationEpochState>(&epoch_id)
703 .map_err(WelcomeError::StorageError)?
704 .ok_or(WelcomeError::VirtualClientsError(
705 VirtualClientsError::MissingDerivationEpochState,
706 ))?;
707 let max_entries = self.message_secrets_store.max_epochs.saturating_add(1);
710 write_vc_emulation_binding_with_pruning(
711 provider.storage(),
712 group_id,
713 self.public_group.group_context().epoch(),
714 epoch_id,
715 max_entries,
716 )
717 .map_err(WelcomeError::StorageError)?;
718 }
719
720 let past_epoch_deletion_policy = self.mls_group_config.past_epoch_deletion_policy().clone();
721
722 let mut mls_group = MlsGroup {
723 mls_group_config: self.mls_group_config,
724 own_leaf_nodes: vec![],
725 aad: vec![],
726 #[cfg(feature = "extensions-draft")]
727 safe_aad: SafeAad::empty(),
728 group_state: MlsGroupState::Operational,
729 public_group: self.public_group,
730 group_epoch_secrets: self.group_epoch_secrets,
731 own_leaf_index: self.own_leaf_index,
732 message_secrets_store: self.message_secrets_store,
733 resumption_psk_store: self.resumption_psk_store,
734 #[cfg(feature = "extensions-draft")]
735 application_export_tree: Some(application_export_tree),
736 #[cfg(feature = "virtual-clients-draft")]
737 emulation_group: self.emulation_group,
738 };
739
740 mls_group
741 .store_epoch_keypairs(provider.storage(), group_keypairs.as_slice())
742 .map_err(WelcomeError::StorageError)?;
743 mls_group.resize_message_secrets_store(&past_epoch_deletion_policy);
745
746 #[cfg(feature = "virtual-clients-draft")]
752 if let Some(material) = self.key_material.vc_welcome_material() {
753 let max_entries = mls_group.message_secrets_store.max_epochs.saturating_add(1);
754 write_vc_emulation_binding_with_pruning(
755 provider.storage(),
756 mls_group.group_id(),
757 mls_group.epoch(),
758 material.epoch_id.clone(),
759 max_entries,
760 )
761 .map_err(WelcomeError::StorageError)?;
762 provider
763 .storage()
764 .delete_retained_key_package_material(&material.key_package_ref)
765 .map_err(WelcomeError::StorageError)?;
766 }
767
768 mls_group
769 .store(provider.storage())
770 .map_err(WelcomeError::StorageError)?;
771
772 Ok(mls_group)
773 }
774
775 pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
780 &self,
781 crypto: &CryptoProvider,
782 label: &str,
783 context: &[u8],
784 key_length: usize,
785 ) -> Result<ExportedSecret<StagedWelcomeExport>, ExportSecretError> {
786 if key_length > u16::MAX as usize {
787 log::error!("Got a key that is larger than u16::MAX");
788 return Err(ExportSecretError::KeyLengthTooLong);
789 }
790
791 Ok(ExportedSecret::new(
792 self.group_epoch_secrets
793 .exporter_secret()
794 .derive_exported_secret(
795 self.group_context().ciphersuite(),
796 crypto,
797 label,
798 context,
799 key_length,
800 )
801 .map_err(LibraryError::unexpected_crypto_error)?,
802 ))
803 }
804}
805
806pub struct PendingBranchWelcome {
815 mls_group_config: MlsGroupJoinConfig,
816 ciphersuite: Ciphersuite,
817 welcome: Welcome,
818 resumption_psk_store: ResumptionPskStore,
819 key_material: WelcomeKeyMaterial,
820 group_secrets: GroupSecrets,
821}
822
823impl PendingBranchWelcome {
824 pub fn parent(&self) -> Option<(GroupId, GroupEpoch)> {
833 self.group_secrets
834 .psks
835 .iter()
836 .find_map(|id| match id.psk() {
837 Psk::Resumption(r) if r.usage() == ResumptionPskUsage::Branch => {
838 Some((r.psk_group_id().clone(), r.psk_epoch()))
839 }
840 _ => None,
841 })
842 }
843
844 pub fn build_from_branch<'a, Provider: OpenMlsProvider>(
852 self,
853 provider: &'a Provider,
854 branch_info: BranchInfo,
855 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
856 let processed_welcome = finish_processed_welcome(
857 provider,
858 &self.mls_group_config,
859 self.ciphersuite,
860 self.resumption_psk_store,
861 self.key_material,
862 self.group_secrets,
863 &self.welcome,
864 Some(&branch_info),
865 )?;
866
867 Ok(JoinBuilder::new(provider, processed_welcome).with_branch_info(branch_info))
868 }
869}
870
871fn decrypt_group_secrets<Provider: OpenMlsProvider>(
882 provider: &Provider,
883 mls_group_config: &MlsGroupJoinConfig,
884 welcome: &Welcome,
885) -> Result<
886 (ResumptionPskStore, WelcomeKeyMaterial, GroupSecrets),
887 WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
888> {
889 let ciphersuite = welcome.ciphersuite();
890 provider
893 .crypto()
894 .supports(ciphersuite)
895 .map_err(|_| WelcomeError::UnsupportedCiphersuite(ciphersuite))?;
896
897 let (resumption_psk_store, key_material) =
898 keys_for_welcome(mls_group_config, welcome, provider)?;
899
900 let Some(egs) =
901 welcome.find_encrypted_group_secret(key_material.key_package_ref(provider.crypto())?)
902 else {
903 return Err(WelcomeError::JoinerSecretNotFound);
904 };
905
906 if let Some(key_package_bundle) = key_material.key_package_bundle() {
910 if welcome.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
911 let e = WelcomeError::CiphersuiteMismatch;
912 log::debug!("new_from_welcome {e:?}");
913 return Err(e);
914 }
915 }
916
917 let group_secrets = GroupSecrets::try_from_ciphertext(
918 key_material.init_private_key(),
919 egs.encrypted_group_secrets(),
920 welcome.encrypted_group_info(),
921 ciphersuite,
922 provider.crypto(),
923 )?;
924
925 PreSharedKeyId::validate_in_welcome(&group_secrets.psks, ciphersuite)?;
927
928 Ok((resumption_psk_store, key_material, group_secrets))
929}
930
931#[allow(clippy::too_many_arguments)]
946fn finish_processed_welcome<Provider: OpenMlsProvider>(
947 provider: &Provider,
948 mls_group_config: &MlsGroupJoinConfig,
949 ciphersuite: Ciphersuite,
950 mut resumption_psk_store: ResumptionPskStore,
951 key_material: WelcomeKeyMaterial,
952 group_secrets: GroupSecrets,
953 welcome: &Welcome,
954 branch_info: Option<&BranchInfo>,
955) -> Result<ProcessedWelcome, WelcomeError<<Provider as OpenMlsProvider>::StorageError>> {
956 if let Some(branch_info) = branch_info {
959 resumption_psk_store.add(0.into(), branch_info.resumption_psk_secret().clone());
960 }
961
962 if let Some(branch_info) = branch_info {
969 let parent_matches = group_secrets
970 .psks
971 .iter()
972 .filter_map(|id| match id.psk() {
973 Psk::Resumption(r) if r.usage() == ResumptionPskUsage::Branch => Some(r),
974 _ => None,
975 })
976 .any(|r| {
977 r.psk_group_id() == branch_info.group_id() && r.psk_epoch() == branch_info.epoch()
978 });
979 if !parent_matches {
980 return Err(WelcomeError::SubgroupParentMismatch);
981 }
982 }
983
984 let psk_secret = {
985 let psks = load_psks(
986 provider.storage(),
987 &resumption_psk_store,
988 &group_secrets.psks,
989 )?;
990
991 PskSecret::new(provider.crypto(), ciphersuite, psks)?
992 };
993
994 let mut key_schedule = KeySchedule::init(
996 ciphersuite,
997 provider.crypto(),
998 &group_secrets.joiner_secret,
999 psk_secret,
1000 )?;
1001
1002 let (welcome_key, welcome_nonce) = key_schedule
1004 .welcome(provider.crypto(), ciphersuite)
1005 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?
1006 .derive_welcome_key_nonce(provider.crypto(), ciphersuite)
1007 .map_err(LibraryError::unexpected_crypto_error)?;
1008
1009 let verifiable_group_info = VerifiableGroupInfo::try_from_ciphertext(
1010 &welcome_key,
1011 &welcome_nonce,
1012 welcome.encrypted_group_info(),
1013 &[],
1014 provider.crypto(),
1015 )?;
1016
1017 let serialized_group_context = verifiable_group_info
1018 .group_context()
1019 .tls_serialize_detached()
1020 .map_err(LibraryError::missing_bound_check)?;
1021
1022 key_schedule.add_context(provider.crypto(), &serialized_group_context)?;
1023
1024 let epoch_secrets = key_schedule.epoch_secrets(provider.crypto(), ciphersuite)?;
1025
1026 if let Some(key_package_bundle) = key_material.key_package_bundle() {
1031 if let Some(required_capabilities) =
1032 verifiable_group_info.extensions().required_capabilities()
1033 {
1034 key_package_bundle
1037 .key_package()
1038 .leaf_node()
1039 .capabilities()
1040 .supports_required_capabilities(required_capabilities)?;
1041 }
1042
1043 if verifiable_group_info.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
1047 let e = WelcomeError::CiphersuiteMismatch;
1048 log::debug!("new_from_welcome {e:?}");
1049 return Err(e);
1050 }
1051 }
1052
1053 Ok(ProcessedWelcome {
1054 mls_group_config: mls_group_config.clone(),
1055 ciphersuite,
1056 group_secrets,
1057 epoch_secrets,
1058 verifiable_group_info,
1059 resumption_psk_store,
1060 key_material,
1061 })
1062}
1063
1064fn keys_for_welcome<Provider: OpenMlsProvider>(
1066 mls_group_config: &MlsGroupJoinConfig,
1067 welcome: &Welcome,
1068 provider: &Provider,
1069) -> Result<
1070 (ResumptionPskStore, WelcomeKeyMaterial),
1071 WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
1072> {
1073 let resumption_psk_store = ResumptionPskStore::new(mls_group_config.number_of_resumption_psks);
1074
1075 for egs in welcome.secrets() {
1076 let hash_ref = egs.new_member();
1077 if let Some(key_package_bundle) = provider
1078 .storage()
1079 .key_package(&hash_ref)
1080 .map_err(WelcomeError::StorageError)?
1081 {
1082 let key_package_bundle: KeyPackageBundle = key_package_bundle;
1083 if !key_package_bundle.key_package().last_resort() {
1084 provider
1085 .storage()
1086 .delete_key_package(
1087 &key_package_bundle.key_package.hash_ref(provider.crypto())?,
1088 )
1089 .map_err(WelcomeError::StorageError)?;
1090 } else {
1091 log::debug!("Key package has last resort extension, not deleting");
1092 }
1093 return Ok((
1094 resumption_psk_store,
1095 WelcomeKeyMaterial::with_key_package_bundle(key_package_bundle),
1096 ));
1097 }
1098
1099 #[cfg(feature = "virtual-clients-draft")]
1100 if let Some(material) =
1101 resolve_vc_welcome_material(provider, welcome.ciphersuite(), &hash_ref)?
1102 {
1103 return Ok((
1105 resumption_psk_store,
1106 WelcomeKeyMaterial::with_vc_welcome_material(material),
1107 ));
1108 }
1109 }
1110
1111 Err(WelcomeError::NoMatchingKeyPackage)
1112}
1113
1114#[cfg(feature = "virtual-clients-draft")]
1127pub(crate) fn resolve_vc_welcome_material<Provider: OpenMlsProvider>(
1128 provider: &Provider,
1129 ciphersuite: Ciphersuite,
1130 hash_ref: &KeyPackageRef,
1131) -> Result<Option<VcWelcomeMaterial>, WelcomeError<<Provider as OpenMlsProvider>::StorageError>> {
1132 let storage = provider.storage();
1133 let Some(material) = storage
1134 .retained_key_package_material::<_, RetainedKeyPackageMaterial>(hash_ref)
1135 .map_err(WelcomeError::StorageError)?
1136 else {
1137 return Ok(None);
1138 };
1139
1140 if material.key_package_ciphersuite != ciphersuite {
1141 return Err(WelcomeError::CiphersuiteMismatch);
1142 }
1143
1144 let crypto = provider.crypto();
1148 let init_key_pair = material
1149 .key_package_seed_secret
1150 .derive_init_key_secret(crypto, ciphersuite)?
1151 .generate_init_key_pair(crypto, ciphersuite)?;
1152 let encryption_keypair = material
1153 .key_package_seed_secret
1154 .derive_encryption_key_secret(crypto, ciphersuite)?
1155 .generate_encryption_key_pair(crypto, ciphersuite)?;
1156
1157 Ok(Some(VcWelcomeMaterial {
1158 key_package_ref: hash_ref.clone(),
1159 epoch_id: material.epoch_id,
1160 leaf_index: material.leaf_index,
1161 generation: material.generation,
1162 key_package_index: material.key_package_index,
1163 init_private_key: init_key_pair.private,
1164 init_key: init_key_pair.public.into(),
1165 encryption_keypair,
1166 }))
1167}
1168
1169#[cfg(feature = "virtual-clients-draft")]
1182fn find_and_validate_vc_own_leaf<Provider: OpenMlsProvider>(
1183 provider: &Provider,
1184 public_group: &PublicGroup,
1185 material: &VcWelcomeMaterial,
1186) -> Result<LeafNodeIndex, WelcomeError<<Provider as OpenMlsProvider>::StorageError>> {
1187 use tls_codec::Serialize as _;
1188
1189 let crypto = provider.crypto();
1190 let derived_encryption_key = material.encryption_keypair.public_key().as_slice().to_vec();
1191
1192 let own_index = public_group
1193 .members()
1194 .find(|m| m.encryption_key == derived_encryption_key)
1195 .map(|m| m.index)
1196 .ok_or(WelcomeError::PublicTreeError(
1197 PublicTreeError::MalformedTree,
1198 ))?;
1199
1200 let own_leaf = public_group
1201 .leaf(own_index)
1202 .ok_or(WelcomeError::PublicTreeError(
1203 PublicTreeError::MalformedTree,
1204 ))?;
1205
1206 let derivation_info = own_leaf
1207 .vc_derivation_info()?
1208 .ok_or(VirtualClientsError::VcComponentNotListed)?;
1209 if derivation_info.epoch_id() != &material.epoch_id {
1210 log::error!("vc: welcome leaf epoch id does not match the retained material");
1211 return Err(VirtualClientsError::DerivationInfoMalformed.into());
1212 }
1213
1214 let state: VcDerivationEpochState = provider
1215 .storage()
1216 .vc_derivation_epoch_state(&material.epoch_id)
1217 .map_err(|e| {
1218 log::error!("vc: load derivation epoch state in welcome staging failed: {e:?}");
1219 VirtualClientsError::StorageError
1220 })?
1221 .ok_or(VirtualClientsError::MissingDerivationEpochState)?;
1222 let (_state_leaf_index, epoch_encryption_key, emulation_ciphersuite) = state.into_parts();
1223
1224 let leaf_encryption_key = own_leaf
1225 .encryption_key()
1226 .tls_serialize_detached()
1227 .map_err(VirtualClientsError::from)?;
1228 let tbe = derivation_info.decrypt(
1231 crypto,
1232 emulation_ciphersuite,
1233 &epoch_encryption_key,
1234 &leaf_encryption_key,
1235 VirtualClientOperationType::KeyPackage,
1236 )?;
1237
1238 let DerivationInfoTbe::KeyPackage {
1239 leaf_index,
1240 generation,
1241 key_package_index,
1242 } = tbe
1243 else {
1244 log::error!("vc: welcome leaf derivation info is not a key-package variant");
1245 return Err(VirtualClientsError::DerivationInfoMalformed.into());
1246 };
1247 if leaf_index != material.leaf_index
1248 || generation != material.generation
1249 || key_package_index != material.key_package_index
1250 {
1251 log::error!("vc: welcome leaf derivation info does not match the retained material");
1252 return Err(VirtualClientsError::DerivationInfoMalformed.into());
1253 }
1254
1255 Ok(own_index)
1256}
1257
1258#[cfg(feature = "virtual-clients-draft")]
1259impl MlsGroup {
1260 pub fn vc_external_commit_join_builder() -> VcExternalCommitJoinBuilder {
1264 VcExternalCommitJoinBuilder::new()
1265 }
1266
1267 pub fn vc_join_at_creation<Provider: OpenMlsProvider>(
1283 provider: &Provider,
1284 join_config: &MlsGroupJoinConfig,
1285 verifiable_group_info: VerifiableGroupInfo,
1286 ratchet_tree: Option<RatchetTreeIn>,
1287 epoch_id: EpochId,
1288 ) -> Result<MlsGroup, VcGroupCreationJoinError<Provider::StorageError>> {
1289 use tls_codec::Serialize as _;
1290
1291 type Error<S> = VcGroupCreationJoinError<S>;
1292
1293 let ratchet_tree = match verifiable_group_info.extensions().ratchet_tree() {
1296 Some(extension) => extension.ratchet_tree().clone(),
1297 None => ratchet_tree.ok_or(Error::MissingRatchetTree)?,
1298 };
1299 let (public_group, group_info) = PublicGroup::from_ratchet_tree(
1300 provider.crypto(),
1301 ratchet_tree,
1302 verifiable_group_info,
1303 ProposalStore::new(),
1304 LeafNodeLifetimePolicy::default(),
1305 )?;
1306 let ciphersuite = public_group.ciphersuite();
1307
1308 if public_group.members().count() != 1 {
1310 return Err(Error::NotASingleLeafTree);
1311 }
1312 let creator_index = LeafNodeIndex::new(0);
1313 let creator_leaf = public_group
1314 .leaf(creator_index)
1315 .ok_or(Error::NotASingleLeafTree)?;
1316
1317 let LeafNodeSource::KeyPackage(_) = creator_leaf.leaf_node_source() else {
1319 return Err(Error::CreatorLeafNotKeyPackageSourced);
1320 };
1321
1322 let derivation_info = creator_leaf
1324 .vc_derivation_info()?
1325 .ok_or(Error::MissingDerivationInfo)?;
1326 if derivation_info.epoch_id() != &epoch_id {
1327 return Err(Error::EpochIdMismatch);
1328 }
1329
1330 let (state, mut operation_tree) = load_vc_epoch_state_and_tree(provider, &epoch_id)?;
1332 let (_leaf_index, epoch_encryption_key, emulation_ciphersuite) = state.into_parts();
1333
1334 let leaf_encryption_key = creator_leaf
1337 .encryption_key()
1338 .tls_serialize_detached()
1339 .map_err(VirtualClientsError::from)?;
1340 let tbe = derivation_info.decrypt(
1341 provider.crypto(),
1342 emulation_ciphersuite,
1343 &epoch_encryption_key,
1344 &leaf_encryption_key,
1345 VirtualClientOperationType::KeyPackage,
1346 )?;
1347 let DerivationInfoTbe::KeyPackage {
1348 leaf_index,
1349 generation,
1350 key_package_index,
1351 } = tbe
1352 else {
1353 return Err(LibraryError::custom("unexpected derivation info variant").into());
1356 };
1357
1358 let operation_secret = operation_tree.derive_operation_secret(
1365 provider.crypto(),
1366 emulation_ciphersuite,
1367 &epoch_id,
1368 leaf_index,
1369 VirtualClientOperationType::KeyPackage,
1370 generation,
1371 b"",
1372 )?;
1373 let key_package_seed = operation_secret.derive_key_package_seed_secret(
1374 provider.crypto(),
1375 ciphersuite,
1376 key_package_index,
1377 )?;
1378 let leaf_keypair = key_package_seed
1379 .derive_encryption_key_secret(provider.crypto(), ciphersuite)?
1380 .generate_encryption_key_pair(provider.crypto(), ciphersuite)?;
1381 if leaf_keypair.public_key() != creator_leaf.encryption_key() {
1382 return Err(Error::LeafKeyMismatch);
1383 }
1384
1385 let serialized_group_context = public_group
1389 .group_context()
1390 .tls_serialize_detached()
1391 .map_err(LibraryError::missing_bound_check)?;
1392 let epoch_secret =
1393 key_package_seed.derive_group_creation_secret(provider.crypto(), ciphersuite)?;
1394 let epoch_secrets =
1395 EpochSecrets::from_epoch_secret(provider.crypto(), ciphersuite, epoch_secret)
1396 .map_err(LibraryError::unexpected_crypto_error)?;
1397 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
1398 serialized_group_context,
1399 public_group.tree_size(),
1400 creator_index,
1401 );
1402 let expected_confirmation_tag = message_secrets
1403 .confirmation_key()
1404 .tag(
1405 provider.crypto(),
1406 ciphersuite,
1407 public_group.group_context().confirmed_transcript_hash(),
1408 )
1409 .map_err(LibraryError::unexpected_crypto_error)?;
1410 if &expected_confirmation_tag != group_info.confirmation_tag() {
1411 return Err(Error::ConfirmationTagMismatch);
1412 }
1413
1414 provider
1417 .storage()
1418 .write_vc_operation_tree(&epoch_id, &operation_tree)
1419 .map_err(Error::StorageError)?;
1420
1421 let message_secrets_store = MessageSecretsStore::new_with_secret(
1422 join_config.past_epoch_deletion_policy(),
1423 message_secrets,
1424 );
1425 let mut resumption_psk_store =
1426 ResumptionPskStore::new(join_config.number_of_resumption_psks);
1427 resumption_psk_store.add(
1428 public_group.group_context().epoch(),
1429 group_epoch_secrets.resumption_psk().clone(),
1430 );
1431
1432 let max_entries = message_secrets_store.max_epochs.saturating_add(1);
1438 write_vc_emulation_binding_with_pruning(
1439 provider.storage(),
1440 public_group.group_id(),
1441 public_group.group_context().epoch(),
1442 epoch_id,
1443 max_entries,
1444 )
1445 .map_err(Error::StorageError)?;
1446
1447 let mls_group = MlsGroup {
1448 mls_group_config: join_config.clone(),
1449 own_leaf_nodes: vec![],
1450 aad: vec![],
1451 #[cfg(feature = "extensions-draft")]
1452 safe_aad: SafeAad::empty(),
1453 group_state: MlsGroupState::Operational,
1454 public_group,
1455 group_epoch_secrets,
1456 own_leaf_index: creator_index,
1457 message_secrets_store,
1458 resumption_psk_store,
1459 #[cfg(feature = "extensions-draft")]
1460 application_export_tree: None,
1461 #[cfg(feature = "virtual-clients-draft")]
1462 emulation_group: false,
1463 };
1464 mls_group
1465 .store(provider.storage())
1466 .map_err(Error::StorageError)?;
1467 mls_group
1468 .store_epoch_keypairs(provider.storage(), &[leaf_keypair])
1469 .map_err(Error::StorageError)?;
1470
1471 Ok(mls_group)
1472 }
1473}
1474
1475#[cfg(feature = "virtual-clients-draft")]
1489#[derive(Debug, Default)]
1490pub struct VcExternalCommitJoinBuilder {
1491 join_config: MlsGroupJoinConfig,
1492 ratchet_tree: Option<RatchetTreeIn>,
1493 lifetime_policy: LeafNodeLifetimePolicy,
1494}
1495
1496#[cfg(feature = "virtual-clients-draft")]
1497impl VcExternalCommitJoinBuilder {
1498 pub fn new() -> Self {
1500 Self::default()
1501 }
1502
1503 pub fn with_config(mut self, join_config: MlsGroupJoinConfig) -> Self {
1505 self.join_config = join_config;
1506 self
1507 }
1508
1509 pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
1514 self.ratchet_tree = Some(ratchet_tree);
1515 self
1516 }
1517
1518 pub fn skip_lifetime_validation(mut self) -> Self {
1523 self.lifetime_policy = LeafNodeLifetimePolicy::Skip;
1524 self
1525 }
1526
1527 pub fn process_commit<Provider: OpenMlsProvider>(
1539 self,
1540 provider: &Provider,
1541 verifiable_group_info: VerifiableGroupInfo,
1542 external_commit: impl Into<ProtocolMessage>,
1543 epoch_id: EpochId,
1544 ) -> Result<StagedVcExternalCommitJoin, VcExternalCommitJoinError<Provider::StorageError>> {
1545 type Error<S> = VcExternalCommitJoinError<S>;
1546
1547 let Self {
1548 join_config,
1549 ratchet_tree,
1550 lifetime_policy,
1551 } = self;
1552
1553 let ratchet_tree = match verifiable_group_info.extensions().ratchet_tree() {
1556 Some(extension) => extension.ratchet_tree().clone(),
1557 None => ratchet_tree.ok_or(Error::MissingRatchetTree)?,
1558 };
1559 let (public_group, _group_info) = PublicGroup::from_ratchet_tree(
1560 provider.crypto(),
1561 ratchet_tree,
1562 verifiable_group_info,
1563 ProposalStore::new(),
1564 lifetime_policy,
1565 )?;
1566
1567 let ciphersuite = public_group.ciphersuite();
1574 let serialized_group_context = public_group
1575 .group_context()
1576 .tls_serialize_detached()
1577 .map_err(LibraryError::missing_bound_check)?;
1578 let own_leaf_index = public_group.leftmost_free_index(std::iter::empty())?;
1579 let init_secret = InitSecret::random(ciphersuite, provider.rand())
1580 .map_err(LibraryError::unexpected_crypto_error)?;
1581 let epoch_secrets =
1582 EpochSecrets::with_init_secret(provider.crypto(), ciphersuite, init_secret)
1583 .map_err(LibraryError::unexpected_crypto_error)?;
1584 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
1585 serialized_group_context,
1586 public_group.tree_size(),
1587 LeafNodeIndex::new(0u32),
1588 );
1589 let message_secrets_store = MessageSecretsStore::new_with_secret(
1592 &PastEpochDeletionPolicy::MaxEpochs(0),
1593 message_secrets,
1594 );
1595 let resumption_psk_store = ResumptionPskStore::new(join_config.number_of_resumption_psks);
1596 let mut group = MlsGroup {
1597 mls_group_config: join_config,
1598 own_leaf_nodes: vec![],
1599 aad: vec![],
1600 #[cfg(feature = "extensions-draft")]
1601 safe_aad: SafeAad::empty(),
1602 group_state: MlsGroupState::Operational,
1603 public_group,
1604 group_epoch_secrets,
1605 own_leaf_index,
1606 message_secrets_store,
1607 resumption_psk_store,
1608 #[cfg(feature = "extensions-draft")]
1609 application_export_tree: None,
1610 emulation_group: false,
1613 };
1614
1615 let processing::UnprotectedMessage::Unverified(unverified) =
1618 group.unprotect_message(provider, external_commit)?
1619 else {
1620 return Err(Error::NotAnExternalCommit);
1621 };
1622 let verified = unverified
1623 .verify(group.ciphersuite(), provider.crypto(), group.version())
1624 .map_err(ProcessMessageError::from)?;
1625 if !matches!(verified.content.sender(), Sender::NewMemberCommit) {
1626 return Err(Error::NotAnExternalCommit);
1627 }
1628 let content = verified.content;
1629 let FramedContentBody::Commit(commit) = content.content() else {
1630 return Err(Error::NotAnExternalCommit);
1631 };
1632
1633 let derivation_info = commit
1638 .path
1639 .as_ref()
1640 .map(|path| path.leaf_node().vc_derivation_info())
1641 .transpose()?
1642 .flatten()
1643 .ok_or(Error::MissingDerivationInfo)?;
1644 if derivation_info.epoch_id() != &epoch_id {
1645 return Err(Error::EpochIdMismatch);
1646 }
1647
1648 let app_data_update_proposals =
1654 committed_app_data_update_proposals(commit, group.proposal_store());
1655
1656 Ok(StagedVcExternalCommitJoin {
1657 group,
1658 content,
1659 app_data_update_proposals,
1660 app_data_updates: None,
1661 })
1662 }
1663}
1664
1665#[cfg(feature = "virtual-clients-draft")]
1681pub struct StagedVcExternalCommitJoin {
1682 group: MlsGroup,
1684 content: AuthenticatedContent,
1686 app_data_update_proposals: Vec<AppDataUpdateProposal>,
1689 app_data_updates: Option<AppDataUpdates>,
1692}
1693
1694#[cfg(feature = "virtual-clients-draft")]
1695impl StagedVcExternalCommitJoin {
1696 pub fn prior_group_context(&self) -> &GroupContext {
1701 self.group.context()
1702 }
1703
1704 pub fn prior_members(&self) -> impl Iterator<Item = Member> + '_ {
1709 self.group.members()
1710 }
1711
1712 pub fn app_data_update_proposals(&self) -> impl Iterator<Item = &AppDataUpdateProposal> {
1716 self.app_data_update_proposals.iter()
1717 }
1718
1719 pub fn app_ephemeral_proposals_for_component_id(
1728 &self,
1729 component_id: ComponentId,
1730 ) -> impl Iterator<Item = &AppEphemeralProposal> {
1731 let proposals = match self.content.content() {
1732 FramedContentBody::Commit(commit) => commit.proposals.as_slice(),
1733 _ => &[],
1735 };
1736 proposals
1737 .iter()
1738 .filter_map(move |proposal_or_ref| match proposal_or_ref {
1739 ProposalOrRef::Proposal(proposal) => match proposal.as_ref() {
1740 Proposal::AppEphemeral(app_ephemeral)
1741 if app_ephemeral.component_id() == component_id =>
1742 {
1743 Some(app_ephemeral.as_ref())
1744 }
1745 _ => None,
1746 },
1747 ProposalOrRef::Reference(_) => None,
1748 })
1749 }
1750
1751 pub fn app_data_dictionary_updater(&self) -> AppDataDictionaryUpdater<'_> {
1754 AppDataDictionaryUpdater::new(self.group.context().app_data_dict())
1755 }
1756
1757 pub fn with_app_data_dictionary_updates(&mut self, app_data_updates: Option<AppDataUpdates>) {
1761 self.app_data_updates = app_data_updates;
1762 }
1763
1764 pub fn into_group<Provider: OpenMlsProvider>(
1773 self,
1774 provider: &Provider,
1775 ) -> Result<MlsGroup, VcExternalCommitJoinError<Provider::StorageError>> {
1776 type Error<S> = VcExternalCommitJoinError<S>;
1777
1778 let Self {
1779 mut group,
1780 content,
1781 app_data_update_proposals,
1782 app_data_updates,
1783 } = self;
1784
1785 if !app_data_update_proposals.is_empty() && app_data_updates.is_none() {
1791 return Err(StageCommitError::ApplyAppDataUpdateError(
1792 ApplyAppDataUpdateError::MissingAppDataUpdates,
1793 )
1794 .into());
1795 }
1796
1797 let FramedContentBody::Commit(commit) = content.content() else {
1798 return Err(LibraryError::custom("staged join without commit content").into());
1800 };
1801
1802 let material = group
1806 .load_vc_commit_material(provider, commit)?
1807 .ok_or(Error::MissingDerivationInfo)?;
1808 let staged = group.stage_commit_with_app_data_updates(
1809 &content,
1810 vec![],
1811 vec![],
1812 app_data_updates,
1813 provider,
1814 Some(material),
1815 )?;
1816 group.merge_staged_commit(provider, staged)?;
1817 let deletion_policy = group.mls_group_config.past_epoch_deletion_policy().clone();
1818 group.resize_message_secrets_store(&deletion_policy);
1819 group
1820 .store(provider.storage())
1821 .map_err(Error::StorageError)?;
1822 Ok(group)
1823 }
1824}
1825
1826#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1829pub enum LeafNodeLifetimePolicy {
1830 #[default]
1834 Verify,
1835
1836 Skip,
1838}
1839
1840pub struct JoinBuilder<'a, Provider: OpenMlsProvider> {
1844 provider: &'a Provider,
1845 processed_welcome: ProcessedWelcome,
1846 ratchet_tree: Option<RatchetTreeIn>,
1847 validate_lifetimes: LeafNodeLifetimePolicy,
1848 replace_old_group: bool,
1849 branch: Option<BranchInfo>,
1852 check_members: bool,
1855}
1856
1857impl<'a, Provider: OpenMlsProvider> JoinBuilder<'a, Provider> {
1858 pub fn new(provider: &'a Provider, processed_welcome: ProcessedWelcome) -> Self {
1860 Self {
1861 provider,
1862 processed_welcome,
1863 ratchet_tree: None,
1864 replace_old_group: false,
1865 validate_lifetimes: LeafNodeLifetimePolicy::Verify,
1866 branch: None,
1867 check_members: true,
1868 }
1869 }
1870
1871 fn with_branch_info(mut self, branch_info: BranchInfo) -> Self {
1874 self.branch = Some(branch_info);
1875 self
1876 }
1877
1878 pub fn check_members(mut self, check_members: bool) -> Self {
1884 self.check_members = check_members;
1885 self
1886 }
1887
1888 pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
1890 self.ratchet_tree = Some(ratchet_tree);
1891 self
1892 }
1893
1894 pub fn replace_old_group(mut self) -> Self {
1896 self.replace_old_group = true;
1897 self
1898 }
1899
1900 pub fn skip_lifetime_validation(mut self) -> Self {
1905 self.validate_lifetimes = LeafNodeLifetimePolicy::Skip;
1906 self
1907 }
1908
1909 pub fn processed_welcome(&self) -> &ProcessedWelcome {
1913 &self.processed_welcome
1914 }
1915
1916 pub fn build(self) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
1918 if let Some(branch_info) = &self.branch {
1922 let group_info = self.processed_welcome.unverified_group_info();
1923 if group_info.group_context().protocol_version() != branch_info.version()
1924 || group_info.ciphersuite() != branch_info.ciphersuite()
1925 {
1926 return Err(WelcomeError::SubgroupParameterMismatch);
1927 }
1928 if group_info.group_context().epoch().as_u64() != 1 {
1929 return Err(WelcomeError::SubgroupEpochInvalid);
1930 }
1931 }
1932
1933 let staged_welcome = self.processed_welcome.into_staged_welcome_inner(
1934 self.provider,
1935 self.ratchet_tree,
1936 self.validate_lifetimes,
1937 self.replace_old_group,
1938 )?;
1939
1940 if let Some(branch_info) = &self.branch {
1943 if self.check_members {
1944 for member in staged_welcome.members() {
1945 if !branch_info
1946 .member_credentials()
1947 .contains(&member.credential)
1948 {
1949 return Err(WelcomeError::SubgroupLeafMismatch);
1950 }
1951 }
1952 }
1953 }
1954
1955 Ok(staged_welcome)
1956 }
1957}