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 reinit::ReInitInfo,
12 },
13 key_packages::KeyPackage,
14 messages::{
15 group_info::{GroupInfo, VerifiableGroupInfo},
16 Welcome,
17 },
18 schedule::{
19 psk::{store::ResumptionPskStore, PreSharedKeyId, Psk, ResumptionPsk, ResumptionPskUsage},
20 EpochSecretsResult,
21 },
22 storage::OpenMlsProvider,
23 treesync::{
24 errors::{DerivePathError, PublicTreeError},
25 node::leaf_node::{Capabilities, LeafNodeParameters},
26 RatchetTreeIn,
27 },
28};
29
30#[cfg(feature = "virtual-clients-draft")]
31use crate::{
32 ciphersuite::hash_ref::KeyPackageRef,
33 component::ComponentId,
34 components::vc_derivation_info::{
35 load_vc_epoch_state_and_tree, register_vc_derivation_epoch,
36 write_vc_emulation_binding_with_pruning, DerivationInfoTbe, EpochId,
37 RetainedKeyPackageMaterial, VcDerivationEpochParams, VcDerivationEpochState,
38 VcWelcomeMaterial, VirtualClientOperationType, VirtualClientsError,
39 },
40 framing::{mls_auth_content::AuthenticatedContent, ProtocolMessage, SafeAad, Sender},
41 group::{
42 config::PastEpochDeletionPolicy,
43 errors::{
44 ProcessMessageError, StageCommitError, VcExternalCommitJoinError,
45 VcGroupCreationJoinError,
46 },
47 mls_group::processing::{
48 committed_app_data_update_proposals, AppDataDictionaryUpdater, AppDataUpdates,
49 },
50 public_group::{errors::ApplyAppDataUpdateError, PublicGroup},
51 },
52 key_packages::has_last_resort_marker,
53 messages::proposals::{AppDataUpdateProposal, AppEphemeralProposal, Proposal, ProposalOrRef},
54 prelude::mls_content::FramedContentBody,
55 schedule::{EpochSecrets, InitSecret},
56 treesync::node::leaf_node::LeafNodeSource,
57};
58
59impl MlsGroup {
60 pub fn builder() -> MlsGroupBuilder {
65 MlsGroupBuilder::new()
66 }
67
68 #[openmls_traits::maybe_async]
71 pub async fn new<Provider: OpenMlsProvider>(
72 provider: &Provider,
73 signer: &impl Signer,
74 mls_group_create_config: &MlsGroupCreateConfig,
75 credential_with_key: CredentialWithKey,
76 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
77 MlsGroupBuilder::new()
78 .build_internal(
79 provider,
80 signer,
81 credential_with_key,
82 Some(mls_group_create_config.clone()),
83 )
84 .await
85 }
86
87 #[openmls_traits::maybe_async]
90 pub async fn new_with_group_id<Provider: OpenMlsProvider>(
91 provider: &Provider,
92 signer: &impl Signer,
93 mls_group_create_config: &MlsGroupCreateConfig,
94 group_id: GroupId,
95 credential_with_key: CredentialWithKey,
96 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
97 MlsGroupBuilder::new()
98 .with_group_id(group_id)
99 .build_internal(
100 provider,
101 signer,
102 credential_with_key,
103 Some(mls_group_create_config.clone()),
104 )
105 .await
106 }
107
108 #[allow(clippy::too_many_arguments)]
123 #[deprecated(
124 since = "0.7.1",
125 note = "Use the `MlsGroup::external_commit_builder` instead."
126 )]
127 #[openmls_traits::maybe_async]
128 pub async fn join_by_external_commit<Provider: OpenMlsProvider>(
129 provider: &Provider,
130 signer: &impl Signer,
131 ratchet_tree: Option<RatchetTreeIn>,
132 verifiable_group_info: VerifiableGroupInfo,
133 mls_group_config: &MlsGroupJoinConfig,
134 capabilities: Option<Capabilities>,
135 extensions: Option<Extensions<LeafNode>>,
136 aad: &[u8],
137 credential_with_key: CredentialWithKey,
138 ) -> Result<(Self, MlsMessageOut, Option<GroupInfo>), ExternalCommitError<Provider::StorageError>>
139 {
140 let mut leaf_node_parameters =
143 LeafNodeParameters::builder().with_extensions(extensions.unwrap_or_default());
144 if let Some(capabilities) = capabilities {
145 leaf_node_parameters = leaf_node_parameters.with_capabilities(capabilities);
146 }
147 let leaf_node_parameters = leaf_node_parameters.build();
148
149 let mut external_commit_builder = ExternalCommitBuilder::new()
150 .with_aad(aad.to_vec())
151 .with_config(mls_group_config.clone());
152
153 if let Some(ratchet_tree) = ratchet_tree {
154 external_commit_builder = external_commit_builder.with_ratchet_tree(ratchet_tree)
155 }
156
157 let (mls_group, commit_message_bundle) = external_commit_builder
158 .build_group(provider, verifiable_group_info, credential_with_key)?
159 .leaf_node_parameters(leaf_node_parameters)
160 .load_psks(provider.storage())
161 .await
162 .map_err(|e| {
163 log::error!("Error loading PSKs for external commit: {e:?}");
164 LibraryError::custom("Error loading PSKs for external commit")
165 })?
166 .build(provider.rand(), provider.crypto(), signer, |_| true)?
167 .finalize(provider)
168 .await?;
169
170 let (commit, _, group_info) = commit_message_bundle.into_contents();
171
172 Ok((mls_group, commit, group_info))
173 }
174}
175
176impl ProcessedWelcome {
177 #[openmls_traits::maybe_async]
184 pub async fn new_from_welcome<Provider: OpenMlsProvider>(
185 provider: &Provider,
186 mls_group_config: &MlsGroupJoinConfig,
187 welcome: Welcome,
188 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
189 Self::new_from_welcome_inner(provider, mls_group_config, welcome, None).await
190 }
191
192 #[openmls_traits::maybe_async]
202 pub(crate) async fn new_from_welcome_inner<Provider: OpenMlsProvider>(
203 provider: &Provider,
204 mls_group_config: &MlsGroupJoinConfig,
205 welcome: Welcome,
206 resumption_info: Option<ResumptionInfo<'_>>,
207 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
208 let (resumption_psk_store, key_material, group_secrets) =
209 decrypt_group_secrets(provider, mls_group_config, &welcome).await?;
210
211 finish_processed_welcome(
212 provider,
213 mls_group_config,
214 welcome.ciphersuite(),
215 resumption_psk_store,
216 key_material,
217 group_secrets,
218 &welcome,
219 resumption_info,
220 )
221 .await
222 }
223
224 pub fn unverified_group_info(&self) -> &VerifiableGroupInfo {
228 &self.verifiable_group_info
229 }
230
231 pub fn psks(&self) -> &[PreSharedKeyId] {
235 &self.group_secrets.psks
236 }
237
238 #[openmls_traits::maybe_async]
241 pub async fn into_staged_welcome<Provider: OpenMlsProvider>(
242 self,
243 provider: &Provider,
244 ratchet_tree: Option<RatchetTreeIn>,
245 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
246 self.into_staged_welcome_inner(
247 provider,
248 ratchet_tree,
249 LeafNodeLifetimePolicy::Verify,
250 false,
251 )
252 .await
253 }
254
255 #[openmls_traits::maybe_async]
258 pub(crate) async fn into_staged_welcome_inner<Provider: OpenMlsProvider>(
259 mut self,
260 provider: &Provider,
261 ratchet_tree: Option<RatchetTreeIn>,
262 validate_lifetimes: LeafNodeLifetimePolicy,
263 replace_old_group: bool,
264 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
265 if !replace_old_group
267 && MlsGroup::load(provider.storage(), self.verifiable_group_info.group_id())
268 .await
269 .map_err(WelcomeError::StorageError)?
270 .is_some()
271 {
272 return Err(WelcomeError::GroupAlreadyExists);
273 }
274
275 let ratchet_tree = match self.verifiable_group_info.extensions().ratchet_tree() {
282 Some(extension) => extension.ratchet_tree().clone(),
283 None => match ratchet_tree {
284 Some(ratchet_tree) => ratchet_tree,
285 None => return Err(WelcomeError::MissingRatchetTree),
286 },
287 };
288
289 let (public_group, _group_info_extensions) = PublicGroup::from_ratchet_tree(
292 provider.crypto(),
293 ratchet_tree,
294 self.verifiable_group_info.clone(),
295 ProposalStore::new(),
296 validate_lifetimes,
297 )?;
298
299 let own_leaf_index = match &self.key_material.inner() {
305 WelcomeKeyMaterialInner::KeyPackage(key_package_bundle) => {
306 let added_leaf_supports_all_group_context_extensions = public_group
310 .group_context()
311 .extensions()
312 .iter()
313 .all(|extension| {
314 key_package_bundle
315 .key_package
316 .leaf_node()
317 .supports_extension(&extension.extension_type())
318 });
319 if !added_leaf_supports_all_group_context_extensions {
320 return Err(WelcomeError::UnsupportedExtensions);
321 }
322
323 public_group
324 .members()
325 .find_map(|m| {
326 if m.signature_key
327 == key_package_bundle
328 .key_package()
329 .leaf_node()
330 .signature_key()
331 .as_slice()
332 {
333 Some(m.index)
334 } else {
335 None
336 }
337 })
338 .ok_or(WelcomeError::PublicTreeError(
339 PublicTreeError::MalformedTree,
340 ))?
341 }
342 #[cfg(feature = "virtual-clients-draft")]
343 WelcomeKeyMaterialInner::VirtualClient(material) => {
344 find_and_validate_vc_own_leaf(provider, &public_group, material).await?
345 }
346 };
347
348 struct KeyScheduleResult {
349 group_epoch_secrets: GroupEpochSecrets,
350 message_secrets: MessageSecrets,
351 #[cfg(feature = "extensions-draft")]
352 application_exporter: ApplicationExportSecret,
353 }
354 let KeyScheduleResult {
355 group_epoch_secrets,
356 message_secrets,
357 #[cfg(feature = "extensions-draft")]
358 application_exporter: application_export_secret,
359 } = {
360 let serialized_group_context = public_group
361 .group_context()
362 .tls_serialize_detached()
363 .map_err(LibraryError::missing_bound_check)?;
364
365 let EpochSecretsResult {
366 epoch_secrets,
367 #[cfg(feature = "extensions-draft")]
368 application_exporter,
369 } = self.epoch_secrets;
370
371 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
372 serialized_group_context,
373 public_group.tree_size(),
374 own_leaf_index,
375 );
376
377 KeyScheduleResult {
378 group_epoch_secrets,
379 message_secrets,
380 #[cfg(feature = "extensions-draft")]
381 application_exporter,
382 }
383 };
384
385 let confirmation_tag = message_secrets
386 .confirmation_key()
387 .tag(
388 provider.crypto(),
389 self.ciphersuite,
390 public_group.group_context().confirmed_transcript_hash(),
391 )
392 .map_err(LibraryError::unexpected_crypto_error)?;
393
394 if &confirmation_tag != public_group.confirmation_tag() {
397 log::error!("Confirmation tag mismatch");
398 log_crypto!(trace, " Got: {:x?}", confirmation_tag);
399 log_crypto!(trace, " Expected: {:x?}", public_group.confirmation_tag());
400
401 if !crate::skip_validation::is_disabled::confirmation_tag() {
404 return Err(WelcomeError::ConfirmationTagMismatch);
405 }
406 }
407
408 let message_secrets_store = MessageSecretsStore::new_with_secret(
409 &PastEpochDeletionPolicy::MaxEpochs(0),
410 message_secrets,
411 );
412
413 let resumption_psk = group_epoch_secrets.resumption_psk();
415 self.resumption_psk_store
416 .add(public_group.group_context().epoch(), resumption_psk.clone());
417
418 let welcome_sender_index = self.verifiable_group_info.signer();
419 let path_keypairs = if let Some(path_secret) = self.group_secrets.path_secret {
420 let (path_keypairs, _commit_secret) = public_group
421 .derive_path_secrets(
422 provider.crypto(),
423 self.ciphersuite,
424 path_secret,
425 welcome_sender_index,
426 own_leaf_index,
427 )
428 .map_err(|e| match e {
429 DerivePathError::LibraryError(e) => e.into(),
430 DerivePathError::PublicKeyMismatch => {
431 WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch)
432 }
433 })?;
434 Some(path_keypairs)
435 } else {
436 None
437 };
438
439 let staged_welcome = StagedWelcome {
440 mls_group_config: self.mls_group_config,
441 public_group,
442 group_epoch_secrets,
443 own_leaf_index,
444 message_secrets_store,
445 #[cfg(feature = "extensions-draft")]
446 application_export_secret,
447 resumption_psk_store: self.resumption_psk_store,
448 verifiable_group_info: self.verifiable_group_info,
449 key_material: self.key_material,
450 path_keypairs,
451 #[cfg(feature = "virtual-clients-draft")]
452 emulation_group: false,
453 };
454
455 Ok(staged_welcome)
456 }
457
458 pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
463 &self,
464 crypto: &CryptoProvider,
465 label: &str,
466 context: &[u8],
467 key_length: usize,
468 ) -> Result<ExportedSecret<ProcessedWelcomeExport>, ExportSecretError> {
469 if key_length > u16::MAX as usize {
470 log::error!("Got a key that is larger than u16::MAX");
471 return Err(ExportSecretError::KeyLengthTooLong);
472 }
473
474 Ok(ExportedSecret::new(
475 self.epoch_secrets
476 .epoch_secrets
477 .exporter_secret()
478 .derive_exported_secret(self.ciphersuite, crypto, label, context, key_length)
479 .map_err(LibraryError::unexpected_crypto_error)?,
480 ))
481 }
482
483 pub fn own_key_package(&self) -> Option<&KeyPackage> {
486 self.key_material
487 .key_package_bundle()
488 .map(|bundle| bundle.key_package())
489 }
490}
491
492impl StagedWelcome {
493 #[openmls_traits::maybe_async]
502 pub async fn new_from_welcome<Provider: OpenMlsProvider>(
503 provider: &Provider,
504 mls_group_config: &MlsGroupJoinConfig,
505 welcome: Welcome,
506 ratchet_tree: Option<RatchetTreeIn>,
507 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
508 let processed_welcome =
509 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome).await?;
510
511 processed_welcome
512 .into_staged_welcome(provider, ratchet_tree)
513 .await
514 }
515
516 #[openmls_traits::maybe_async]
521 pub async fn build_from_welcome<'a, Provider: OpenMlsProvider>(
522 provider: &'a Provider,
523 mls_group_config: &MlsGroupJoinConfig,
524 welcome: Welcome,
525 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
526 let processed_welcome =
527 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome).await?;
528
529 Ok(JoinBuilder::new(provider, processed_welcome))
531 }
532
533 #[openmls_traits::maybe_async]
572 pub async fn build_from_branch<'a, Provider: OpenMlsProvider>(
573 provider: &'a Provider,
574 mls_group_config: &MlsGroupJoinConfig,
575 welcome: Welcome,
576 branch_info: BranchInfo,
577 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
578 Self::process_resuming_welcome(provider, mls_group_config, welcome)
579 .await?
580 .build_from_branch(provider, branch_info)
581 .await
582 }
583
584 #[openmls_traits::maybe_async]
620 pub async fn build_from_reinit<'a, Provider: OpenMlsProvider>(
621 provider: &'a Provider,
622 mls_group_config: &MlsGroupJoinConfig,
623 welcome: Welcome,
624 reinit_info: ReInitInfo,
625 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
626 Self::process_resuming_welcome(provider, mls_group_config, welcome)
627 .await?
628 .build_from_reinit(provider, reinit_info)
629 .await
630 }
631
632 #[openmls_traits::maybe_async]
643 pub async fn process_resuming_welcome<Provider: OpenMlsProvider>(
644 provider: &Provider,
645 mls_group_config: &MlsGroupJoinConfig,
646 welcome: Welcome,
647 ) -> Result<PendingResumingWelcome, WelcomeError<Provider::StorageError>> {
648 let (resumption_psk_store, key_material, group_secrets) =
649 decrypt_group_secrets(provider, mls_group_config, &welcome).await?;
650
651 Ok(PendingResumingWelcome {
652 mls_group_config: mls_group_config.clone(),
653 ciphersuite: welcome.ciphersuite(),
654 welcome,
655 resumption_psk_store,
656 key_material,
657 group_secrets,
658 })
659 }
660
661 pub fn welcome_sender_index(&self) -> LeafNodeIndex {
665 self.verifiable_group_info.signer()
666 }
667
668 pub fn welcome_sender(&self) -> Result<&LeafNode, LibraryError> {
672 let sender_index = self.welcome_sender_index();
673 self.public_group
674 .leaf(sender_index)
675 .ok_or_else(|| LibraryError::custom("no leaf with given welcome sender index exists"))
676 }
677
678 pub fn own_leaf_index(&self) -> LeafNodeIndex {
680 self.own_leaf_index
681 }
682
683 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
685 self.public_group.leaf(self.own_leaf_index())
686 }
687
688 pub fn group_context(&self) -> &GroupContext {
690 self.public_group.group_context()
691 }
692
693 pub fn members(&self) -> impl Iterator<Item = Member> + '_ {
695 self.public_group.members()
696 }
697
698 #[cfg(feature = "extensions-draft")]
700 pub fn application_export_secret(&self) -> &ApplicationExportSecret {
701 &self.application_export_secret
702 }
703
704 #[cfg(feature = "virtual-clients-draft")]
714 pub fn emulation_group(mut self, emulation_group: bool) -> Self {
715 self.emulation_group = emulation_group;
716 self
717 }
718
719 #[openmls_traits::maybe_async]
721 pub async fn into_group<Provider: OpenMlsProvider>(
722 self,
723 provider: &Provider,
724 ) -> Result<MlsGroup, WelcomeError<Provider::StorageError>> {
725 let group_keypairs = if let Some(path_keypairs) = self.path_keypairs {
728 let mut keypairs = vec![self.key_material.encryption_key_pair()];
729 keypairs.extend_from_slice(&path_keypairs);
730 keypairs
731 } else {
732 vec![self.key_material.encryption_key_pair()]
733 };
734
735 #[cfg(feature = "extensions-draft")]
736 #[cfg_attr(not(feature = "virtual-clients-draft"), allow(unused_mut))]
737 let mut application_export_tree =
738 ApplicationExportTree::new(self.application_export_secret);
739
740 #[cfg(feature = "virtual-clients-draft")]
743 if self.emulation_group {
744 register_vc_derivation_epoch(
745 provider.crypto(),
746 provider.storage(),
747 Some(&mut application_export_tree),
748 VcDerivationEpochParams::for_public_group(
749 &self.public_group,
750 self.own_leaf_index,
751 self.mls_group_config
752 .vc_derivation_epoch_retention_policy()
753 .clone(),
754 ),
755 )
756 .await?;
757 }
758
759 #[cfg(feature = "virtual-clients-draft")]
765 if let Some(derivation_info) = self
766 .public_group
767 .leaf(self.own_leaf_index)
768 .map(LeafNode::vc_derivation_info)
769 .transpose()?
770 .flatten()
771 {
772 let epoch_id = derivation_info.epoch_id().clone();
773 let group_id = self.public_group.group_id();
774 provider
775 .storage()
776 .vc_derivation_epoch_state::<_, VcDerivationEpochState>(&epoch_id)
777 .await
778 .map_err(WelcomeError::StorageError)?
779 .ok_or(WelcomeError::VirtualClientsError(
780 VirtualClientsError::MissingDerivationEpochState,
781 ))?;
782 let max_entries = self.message_secrets_store.max_epochs.saturating_add(1);
785 write_vc_emulation_binding_with_pruning(
786 provider.storage(),
787 group_id,
788 self.public_group.group_context().epoch(),
789 epoch_id,
790 max_entries,
791 )
792 .await
793 .map_err(WelcomeError::StorageError)?;
794 }
795
796 let past_epoch_deletion_policy = self.mls_group_config.past_epoch_deletion_policy().clone();
797
798 let mut mls_group = MlsGroup {
799 mls_group_config: self.mls_group_config,
800 own_leaf_nodes: vec![],
801 aad: vec![],
802 #[cfg(feature = "extensions-draft")]
803 safe_aad: SafeAad::empty(),
804 group_state: MlsGroupState::Operational,
805 public_group: self.public_group,
806 group_epoch_secrets: self.group_epoch_secrets,
807 own_leaf_index: self.own_leaf_index,
808 message_secrets_store: self.message_secrets_store,
809 resumption_psk_store: self.resumption_psk_store,
810 #[cfg(feature = "extensions-draft")]
811 application_export_tree: Some(application_export_tree),
812 #[cfg(feature = "virtual-clients-draft")]
813 emulation_group: self.emulation_group,
814 };
815
816 mls_group
817 .store_epoch_keypairs(provider.storage(), group_keypairs.as_slice())
818 .await
819 .map_err(WelcomeError::StorageError)?;
820 mls_group.resize_message_secrets_store(&past_epoch_deletion_policy);
822
823 #[cfg(feature = "virtual-clients-draft")]
831 if let Some(material) = self.key_material.vc_welcome_material() {
832 let max_entries = mls_group.message_secrets_store.max_epochs.saturating_add(1);
833 write_vc_emulation_binding_with_pruning(
834 provider.storage(),
835 mls_group.group_id(),
836 mls_group.epoch(),
837 material.epoch_id.clone(),
838 max_entries,
839 )
840 .await
841 .map_err(WelcomeError::StorageError)?;
842 if !material.last_resort {
843 provider
844 .storage()
845 .delete_retained_key_package_material(&material.key_package_ref)
846 .await
847 .map_err(WelcomeError::StorageError)?;
848 }
849 }
850
851 #[cfg(feature = "virtual-clients-draft")]
855 if let Some(key_package_bundle) = self.key_material.key_package_bundle() {
856 if !key_package_bundle.key_package().last_resort()
857 && join_consumes_key_package(key_package_bundle)?
858 {
859 provider
860 .storage()
861 .delete_key_package(
862 &key_package_bundle
863 .key_package()
864 .hash_ref(provider.crypto())?,
865 )
866 .await
867 .map_err(WelcomeError::StorageError)?;
868 }
869 }
870
871 mls_group
872 .store(provider.storage())
873 .await
874 .map_err(WelcomeError::StorageError)?;
875
876 Ok(mls_group)
877 }
878
879 pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
884 &self,
885 crypto: &CryptoProvider,
886 label: &str,
887 context: &[u8],
888 key_length: usize,
889 ) -> Result<ExportedSecret<StagedWelcomeExport>, ExportSecretError> {
890 if key_length > u16::MAX as usize {
891 log::error!("Got a key that is larger than u16::MAX");
892 return Err(ExportSecretError::KeyLengthTooLong);
893 }
894
895 Ok(ExportedSecret::new(
896 self.group_epoch_secrets
897 .exporter_secret()
898 .derive_exported_secret(
899 self.group_context().ciphersuite(),
900 crypto,
901 label,
902 context,
903 key_length,
904 )
905 .map_err(LibraryError::unexpected_crypto_error)?,
906 ))
907 }
908}
909
910pub struct PendingResumingWelcome {
920 mls_group_config: MlsGroupJoinConfig,
921 ciphersuite: Ciphersuite,
922 welcome: Welcome,
923 resumption_psk_store: ResumptionPskStore,
924 key_material: WelcomeKeyMaterial,
925 group_secrets: GroupSecrets,
926}
927
928impl PendingResumingWelcome {
929 pub fn required_resumption_secret(&self) -> Option<&ResumptionPsk> {
933 for psk_id in &self.group_secrets.psks {
934 if let Psk::Resumption(resumption_psk) = &psk_id.psk {
935 match resumption_psk.usage {
936 ResumptionPskUsage::Reinit | ResumptionPskUsage::Branch => {
937 return Some(resumption_psk)
938 }
939 _ => {}
940 }
941 }
942 }
943 None
944 }
945
946 #[openmls_traits::maybe_async]
952 pub async fn build<Provider: OpenMlsProvider>(
953 self,
954 provider: &Provider,
955 ) -> Result<ProcessedWelcome, WelcomeError<Provider::StorageError>> {
956 let processed_welcome: ProcessedWelcome = finish_processed_welcome(
957 provider,
958 &self.mls_group_config,
959 self.ciphersuite,
960 self.resumption_psk_store,
961 self.key_material,
962 self.group_secrets,
963 &self.welcome,
964 None,
965 )
966 .await?;
967
968 Ok(processed_welcome)
969 }
970
971 #[openmls_traits::maybe_async]
978 pub async fn build_from_reinit<'a, Provider: OpenMlsProvider>(
979 self,
980 provider: &'a Provider,
981 reinit_info: ReInitInfo,
982 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
983 let processed_welcome = finish_processed_welcome(
984 provider,
985 &self.mls_group_config,
986 self.ciphersuite,
987 self.resumption_psk_store,
988 self.key_material,
989 self.group_secrets,
990 &self.welcome,
991 Some(ResumptionInfo::ReInit(&reinit_info)),
992 )
993 .await?;
994
995 Ok(JoinBuilder::new(provider, processed_welcome).with_reinit_info(reinit_info))
996 }
997
998 #[openmls_traits::maybe_async]
1005 pub async fn build_from_branch<'a, Provider: OpenMlsProvider>(
1006 self,
1007 provider: &'a Provider,
1008 branch_info: BranchInfo,
1009 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
1010 let processed_welcome = finish_processed_welcome(
1011 provider,
1012 &self.mls_group_config,
1013 self.ciphersuite,
1014 self.resumption_psk_store,
1015 self.key_material,
1016 self.group_secrets,
1017 &self.welcome,
1018 Some(ResumptionInfo::Branch(&branch_info)),
1019 )
1020 .await?;
1021
1022 Ok(JoinBuilder::new(provider, processed_welcome).with_branch_info(branch_info))
1023 }
1024}
1025
1026pub(crate) enum ResumptionInfo<'a> {
1027 ReInit(&'a ReInitInfo),
1028 Branch(&'a BranchInfo),
1029}
1030
1031#[openmls_traits::maybe_async]
1043async fn decrypt_group_secrets<Provider: OpenMlsProvider>(
1044 provider: &Provider,
1045 mls_group_config: &MlsGroupJoinConfig,
1046 welcome: &Welcome,
1047) -> Result<
1048 (ResumptionPskStore, WelcomeKeyMaterial, GroupSecrets),
1049 WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
1050> {
1051 let ciphersuite = welcome.ciphersuite();
1052 provider
1055 .crypto()
1056 .supports(ciphersuite)
1057 .map_err(|_| WelcomeError::UnsupportedCiphersuite(ciphersuite))?;
1058
1059 let (resumption_psk_store, key_material) =
1060 keys_for_welcome(mls_group_config, welcome, provider).await?;
1061
1062 let Some(egs) =
1063 welcome.find_encrypted_group_secret(key_material.key_package_ref(provider.crypto())?)
1064 else {
1065 return Err(WelcomeError::JoinerSecretNotFound);
1066 };
1067
1068 if let Some(key_package_bundle) = key_material.key_package_bundle() {
1072 if welcome.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
1073 let e = WelcomeError::CiphersuiteMismatch;
1074 log::debug!("new_from_welcome {e:?}");
1075 return Err(e);
1076 }
1077 }
1078
1079 let group_secrets = GroupSecrets::try_from_ciphertext(
1080 key_material.init_private_key(),
1081 egs.encrypted_group_secrets(),
1082 welcome.encrypted_group_info(),
1083 ciphersuite,
1084 provider.crypto(),
1085 )?;
1086
1087 PreSharedKeyId::validate_in_welcome(&group_secrets.psks, ciphersuite)?;
1089
1090 Ok((resumption_psk_store, key_material, group_secrets))
1091}
1092
1093#[allow(clippy::too_many_arguments)]
1108#[openmls_traits::maybe_async]
1109async fn finish_processed_welcome<Provider: OpenMlsProvider>(
1110 provider: &Provider,
1111 mls_group_config: &MlsGroupJoinConfig,
1112 ciphersuite: Ciphersuite,
1113 mut resumption_psk_store: ResumptionPskStore,
1114 key_material: WelcomeKeyMaterial,
1115 group_secrets: GroupSecrets,
1116 welcome: &Welcome,
1117 resumption_info: Option<ResumptionInfo<'_>>,
1118) -> Result<ProcessedWelcome, WelcomeError<<Provider as OpenMlsProvider>::StorageError>> {
1119 if let Some(resumption_info) = resumption_info {
1120 match resumption_info {
1124 ResumptionInfo::Branch(branch_info) => {
1125 resumption_psk_store.add(0.into(), branch_info.resumption_psk_secret().clone());
1126
1127 let parent_matches = group_secrets
1134 .psks
1135 .iter()
1136 .filter_map(|id| match id.psk() {
1137 Psk::Resumption(r) if r.usage() == ResumptionPskUsage::Branch => Some(r),
1138 _ => None,
1139 })
1140 .any(|r| {
1141 r.psk_group_id() == branch_info.group_id()
1142 && r.psk_epoch() == branch_info.epoch()
1143 });
1144 if !parent_matches {
1145 return Err(WelcomeError::SubgroupParentMismatch);
1146 }
1147 }
1148 ResumptionInfo::ReInit(reinit_info) => {
1149 resumption_psk_store.add(0.into(), reinit_info.resumption_psk_secret().clone());
1150
1151 let matches_reinit = group_secrets
1153 .psks
1154 .iter()
1155 .filter_map(|id| match id.psk() {
1156 Psk::Resumption(r) if r.usage() == ResumptionPskUsage::Reinit => Some(r),
1157 _ => None,
1158 })
1159 .any(|r| {
1160 r.psk_group_id() == reinit_info.old_group_id()
1161 && r.psk_epoch() == reinit_info.old_group_epoch()
1162 });
1163 if !matches_reinit {
1164 return Err(WelcomeError::ReInitPredecessorMismatch);
1165 }
1166 }
1167 }
1168 }
1169
1170 let psk_secret = {
1171 let psks = load_psks(
1172 provider.storage(),
1173 &resumption_psk_store,
1174 &group_secrets.psks,
1175 )
1176 .await?;
1177
1178 PskSecret::new(provider.crypto(), ciphersuite, psks)?
1179 };
1180
1181 let mut key_schedule = KeySchedule::init(
1183 ciphersuite,
1184 provider.crypto(),
1185 &group_secrets.joiner_secret,
1186 psk_secret,
1187 )?;
1188
1189 let (welcome_key, welcome_nonce) = key_schedule
1191 .welcome(provider.crypto(), ciphersuite)
1192 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?
1193 .derive_welcome_key_nonce(provider.crypto(), ciphersuite)
1194 .map_err(LibraryError::unexpected_crypto_error)?;
1195
1196 let verifiable_group_info = VerifiableGroupInfo::try_from_ciphertext(
1197 &welcome_key,
1198 &welcome_nonce,
1199 welcome.encrypted_group_info(),
1200 &[],
1201 provider.crypto(),
1202 )?;
1203
1204 let serialized_group_context = verifiable_group_info
1205 .group_context()
1206 .tls_serialize_detached()
1207 .map_err(LibraryError::missing_bound_check)?;
1208
1209 key_schedule.add_context(provider.crypto(), &serialized_group_context)?;
1210
1211 let epoch_secrets = key_schedule.epoch_secrets(provider.crypto(), ciphersuite)?;
1212
1213 if let Some(key_package_bundle) = key_material.key_package_bundle() {
1218 if let Some(required_capabilities) =
1219 verifiable_group_info.extensions().required_capabilities()
1220 {
1221 key_package_bundle
1224 .key_package()
1225 .leaf_node()
1226 .capabilities()
1227 .supports_required_capabilities(required_capabilities)?;
1228 }
1229
1230 if verifiable_group_info.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
1234 let e = WelcomeError::CiphersuiteMismatch;
1235 log::debug!("new_from_welcome {e:?}");
1236 return Err(e);
1237 }
1238 }
1239
1240 Ok(ProcessedWelcome {
1241 mls_group_config: mls_group_config.clone(),
1242 ciphersuite,
1243 group_secrets,
1244 epoch_secrets,
1245 verifiable_group_info,
1246 resumption_psk_store,
1247 key_material,
1248 })
1249}
1250
1251#[openmls_traits::maybe_async]
1253async fn keys_for_welcome<Provider: OpenMlsProvider>(
1254 mls_group_config: &MlsGroupJoinConfig,
1255 welcome: &Welcome,
1256 provider: &Provider,
1257) -> Result<
1258 (ResumptionPskStore, WelcomeKeyMaterial),
1259 WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
1260> {
1261 let resumption_psk_store = ResumptionPskStore::new(mls_group_config.number_of_resumption_psks);
1262
1263 for egs in welcome.secrets() {
1264 let hash_ref = egs.new_member();
1265 if let Some(key_package_bundle) = provider
1266 .storage()
1267 .key_package(&hash_ref)
1268 .await
1269 .map_err(WelcomeError::StorageError)?
1270 {
1271 let key_package_bundle: KeyPackageBundle = key_package_bundle;
1272 if key_package_bundle.key_package().last_resort() {
1273 log::debug!("KeyPackage has a last-resort marker, not deleting");
1274 } else if !join_consumes_key_package(&key_package_bundle)? {
1275 provider
1276 .storage()
1277 .delete_key_package(
1278 &key_package_bundle.key_package.hash_ref(provider.crypto())?,
1279 )
1280 .await
1281 .map_err(WelcomeError::StorageError)?;
1282 }
1283 return Ok((
1284 resumption_psk_store,
1285 WelcomeKeyMaterial::with_key_package_bundle(key_package_bundle),
1286 ));
1287 }
1288
1289 #[cfg(feature = "virtual-clients-draft")]
1290 if let Some(material) =
1291 resolve_vc_welcome_material(provider, welcome.ciphersuite(), &hash_ref).await?
1292 {
1293 return Ok((
1295 resumption_psk_store,
1296 WelcomeKeyMaterial::with_vc_welcome_material(material),
1297 ));
1298 }
1299 }
1300
1301 Err(WelcomeError::NoMatchingKeyPackage)
1302}
1303
1304fn join_consumes_key_package<StorageError>(
1312 key_package_bundle: &KeyPackageBundle,
1313) -> Result<bool, WelcomeError<StorageError>> {
1314 #[cfg(feature = "virtual-clients-draft")]
1315 {
1316 Ok(key_package_bundle
1317 .key_package()
1318 .leaf_node()
1319 .vc_derivation_info()?
1320 .is_some())
1321 }
1322 #[cfg(not(feature = "virtual-clients-draft"))]
1323 {
1324 let _ = key_package_bundle;
1325 Ok(false)
1326 }
1327}
1328
1329#[cfg(feature = "virtual-clients-draft")]
1342#[openmls_traits::maybe_async]
1343pub(crate) async fn resolve_vc_welcome_material<Provider: OpenMlsProvider>(
1344 provider: &Provider,
1345 ciphersuite: Ciphersuite,
1346 hash_ref: &KeyPackageRef,
1347) -> Result<Option<VcWelcomeMaterial>, WelcomeError<<Provider as OpenMlsProvider>::StorageError>> {
1348 let storage = provider.storage();
1349 let Some(material) = storage
1350 .retained_key_package_material::<_, RetainedKeyPackageMaterial>(hash_ref)
1351 .await
1352 .map_err(WelcomeError::StorageError)?
1353 else {
1354 return Ok(None);
1355 };
1356
1357 if material.key_package_ciphersuite != ciphersuite {
1358 return Err(WelcomeError::CiphersuiteMismatch);
1359 }
1360
1361 let crypto = provider.crypto();
1365 let init_key_pair = material
1366 .key_package_seed_secret
1367 .derive_init_key_secret(crypto, ciphersuite)?
1368 .generate_init_key_pair(crypto, ciphersuite)?;
1369 let encryption_keypair = material
1370 .key_package_seed_secret
1371 .derive_encryption_key_secret(crypto, ciphersuite)?
1372 .generate_encryption_key_pair(crypto, ciphersuite)?;
1373
1374 Ok(Some(VcWelcomeMaterial {
1375 key_package_ref: hash_ref.clone(),
1376 epoch_id: material.epoch_id,
1377 leaf_index: material.leaf_index,
1378 generation: material.generation,
1379 key_package_index: material.key_package_index,
1380 init_private_key: init_key_pair.private,
1381 init_key: init_key_pair.public.into(),
1382 encryption_keypair,
1383 last_resort: has_last_resort_marker(&material.key_package_extensions),
1384 }))
1385}
1386
1387#[cfg(feature = "virtual-clients-draft")]
1400#[openmls_traits::maybe_async]
1401async fn find_and_validate_vc_own_leaf<Provider: OpenMlsProvider>(
1402 provider: &Provider,
1403 public_group: &PublicGroup,
1404 material: &VcWelcomeMaterial,
1405) -> Result<LeafNodeIndex, WelcomeError<<Provider as OpenMlsProvider>::StorageError>> {
1406 use tls_codec::Serialize as _;
1407
1408 let crypto = provider.crypto();
1409 let derived_encryption_key = material.encryption_keypair.public_key().as_slice().to_vec();
1410
1411 let own_index = public_group
1412 .members()
1413 .find(|m| m.encryption_key == derived_encryption_key)
1414 .map(|m| m.index)
1415 .ok_or(WelcomeError::PublicTreeError(
1416 PublicTreeError::MalformedTree,
1417 ))?;
1418
1419 let own_leaf = public_group
1420 .leaf(own_index)
1421 .ok_or(WelcomeError::PublicTreeError(
1422 PublicTreeError::MalformedTree,
1423 ))?;
1424
1425 let derivation_info = own_leaf
1426 .vc_derivation_info()?
1427 .ok_or(VirtualClientsError::VcComponentNotListed)?;
1428 if derivation_info.epoch_id() != &material.epoch_id {
1429 log::error!("vc: welcome leaf epoch id does not match the retained material");
1430 return Err(VirtualClientsError::DerivationInfoMalformed.into());
1431 }
1432
1433 let state: VcDerivationEpochState = provider
1434 .storage()
1435 .vc_derivation_epoch_state(&material.epoch_id)
1436 .await
1437 .map_err(|e| {
1438 log::error!("vc: load derivation epoch state in welcome staging failed: {e:?}");
1439 VirtualClientsError::StorageError
1440 })?
1441 .ok_or(VirtualClientsError::MissingDerivationEpochState)?;
1442 let (_state_leaf_index, epoch_encryption_key, emulation_ciphersuite) = state.into_parts();
1443
1444 let leaf_encryption_key = own_leaf
1445 .encryption_key()
1446 .tls_serialize_detached()
1447 .map_err(VirtualClientsError::from)?;
1448 let tbe = derivation_info.decrypt(
1451 crypto,
1452 emulation_ciphersuite,
1453 &epoch_encryption_key,
1454 &leaf_encryption_key,
1455 VirtualClientOperationType::KeyPackage,
1456 )?;
1457
1458 let DerivationInfoTbe::KeyPackage {
1459 leaf_index,
1460 generation,
1461 key_package_index,
1462 } = tbe
1463 else {
1464 log::error!("vc: welcome leaf derivation info is not a key-package variant");
1465 return Err(VirtualClientsError::DerivationInfoMalformed.into());
1466 };
1467 if leaf_index != material.leaf_index
1468 || generation != material.generation
1469 || key_package_index != material.key_package_index
1470 {
1471 log::error!("vc: welcome leaf derivation info does not match the retained material");
1472 return Err(VirtualClientsError::DerivationInfoMalformed.into());
1473 }
1474
1475 Ok(own_index)
1476}
1477
1478#[cfg(feature = "virtual-clients-draft")]
1479impl MlsGroup {
1480 pub fn vc_external_commit_join_builder() -> VcExternalCommitJoinBuilder {
1484 VcExternalCommitJoinBuilder::new()
1485 }
1486
1487 #[openmls_traits::maybe_async]
1503 pub async fn vc_join_at_creation<Provider: OpenMlsProvider>(
1504 provider: &Provider,
1505 join_config: &MlsGroupJoinConfig,
1506 verifiable_group_info: VerifiableGroupInfo,
1507 ratchet_tree: Option<RatchetTreeIn>,
1508 epoch_id: EpochId,
1509 ) -> Result<MlsGroup, VcGroupCreationJoinError<Provider::StorageError>> {
1510 use tls_codec::Serialize as _;
1511
1512 type Error<S> = VcGroupCreationJoinError<S>;
1513
1514 let ratchet_tree = match verifiable_group_info.extensions().ratchet_tree() {
1517 Some(extension) => extension.ratchet_tree().clone(),
1518 None => ratchet_tree.ok_or(Error::MissingRatchetTree)?,
1519 };
1520 let (public_group, group_info) = PublicGroup::from_ratchet_tree(
1521 provider.crypto(),
1522 ratchet_tree,
1523 verifiable_group_info,
1524 ProposalStore::new(),
1525 LeafNodeLifetimePolicy::default(),
1526 )?;
1527 let ciphersuite = public_group.ciphersuite();
1528
1529 if public_group.members().count() != 1 {
1531 return Err(Error::NotASingleLeafTree);
1532 }
1533 let creator_index = LeafNodeIndex::new(0);
1534 let creator_leaf = public_group
1535 .leaf(creator_index)
1536 .ok_or(Error::NotASingleLeafTree)?;
1537
1538 let LeafNodeSource::KeyPackage(_) = creator_leaf.leaf_node_source() else {
1540 return Err(Error::CreatorLeafNotKeyPackageSourced);
1541 };
1542
1543 let derivation_info = creator_leaf
1545 .vc_derivation_info()?
1546 .ok_or(Error::MissingDerivationInfo)?;
1547 if derivation_info.epoch_id() != &epoch_id {
1548 return Err(Error::EpochIdMismatch);
1549 }
1550
1551 let (state, mut operation_tree) = load_vc_epoch_state_and_tree(provider, &epoch_id).await?;
1553 let (_leaf_index, epoch_encryption_key, emulation_ciphersuite) = state.into_parts();
1554
1555 let leaf_encryption_key = creator_leaf
1558 .encryption_key()
1559 .tls_serialize_detached()
1560 .map_err(VirtualClientsError::from)?;
1561 let tbe = derivation_info.decrypt(
1562 provider.crypto(),
1563 emulation_ciphersuite,
1564 &epoch_encryption_key,
1565 &leaf_encryption_key,
1566 VirtualClientOperationType::KeyPackage,
1567 )?;
1568 let DerivationInfoTbe::KeyPackage {
1569 leaf_index,
1570 generation,
1571 key_package_index,
1572 } = tbe
1573 else {
1574 return Err(LibraryError::custom("unexpected derivation info variant").into());
1577 };
1578
1579 let operation_secret = operation_tree.derive_operation_secret(
1586 provider.crypto(),
1587 emulation_ciphersuite,
1588 &epoch_id,
1589 leaf_index,
1590 VirtualClientOperationType::KeyPackage,
1591 generation,
1592 b"",
1593 )?;
1594 let key_package_seed = operation_secret.derive_key_package_seed_secret(
1595 provider.crypto(),
1596 ciphersuite,
1597 key_package_index,
1598 )?;
1599 let leaf_keypair = key_package_seed
1600 .derive_encryption_key_secret(provider.crypto(), ciphersuite)?
1601 .generate_encryption_key_pair(provider.crypto(), ciphersuite)?;
1602 if leaf_keypair.public_key() != creator_leaf.encryption_key() {
1603 return Err(Error::LeafKeyMismatch);
1604 }
1605
1606 let serialized_group_context = public_group
1610 .group_context()
1611 .tls_serialize_detached()
1612 .map_err(LibraryError::missing_bound_check)?;
1613 let epoch_secret =
1614 key_package_seed.derive_group_creation_secret(provider.crypto(), ciphersuite)?;
1615 let epoch_secrets =
1616 EpochSecrets::from_epoch_secret(provider.crypto(), ciphersuite, epoch_secret)
1617 .map_err(LibraryError::unexpected_crypto_error)?;
1618 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
1619 serialized_group_context,
1620 public_group.tree_size(),
1621 creator_index,
1622 );
1623 let expected_confirmation_tag = message_secrets
1624 .confirmation_key()
1625 .tag(
1626 provider.crypto(),
1627 ciphersuite,
1628 public_group.group_context().confirmed_transcript_hash(),
1629 )
1630 .map_err(LibraryError::unexpected_crypto_error)?;
1631 if &expected_confirmation_tag != group_info.confirmation_tag() {
1632 return Err(Error::ConfirmationTagMismatch);
1633 }
1634
1635 provider
1638 .storage()
1639 .write_vc_operation_tree(&epoch_id, &operation_tree)
1640 .await
1641 .map_err(Error::StorageError)?;
1642
1643 let message_secrets_store = MessageSecretsStore::new_with_secret(
1644 join_config.past_epoch_deletion_policy(),
1645 message_secrets,
1646 );
1647 let mut resumption_psk_store =
1648 ResumptionPskStore::new(join_config.number_of_resumption_psks);
1649 resumption_psk_store.add(
1650 public_group.group_context().epoch(),
1651 group_epoch_secrets.resumption_psk().clone(),
1652 );
1653
1654 let max_entries = message_secrets_store.max_epochs.saturating_add(1);
1660 write_vc_emulation_binding_with_pruning(
1661 provider.storage(),
1662 public_group.group_id(),
1663 public_group.group_context().epoch(),
1664 epoch_id,
1665 max_entries,
1666 )
1667 .await
1668 .map_err(Error::StorageError)?;
1669
1670 let mls_group = MlsGroup {
1671 mls_group_config: join_config.clone(),
1672 own_leaf_nodes: vec![],
1673 aad: vec![],
1674 #[cfg(feature = "extensions-draft")]
1675 safe_aad: SafeAad::empty(),
1676 group_state: MlsGroupState::Operational,
1677 public_group,
1678 group_epoch_secrets,
1679 own_leaf_index: creator_index,
1680 message_secrets_store,
1681 resumption_psk_store,
1682 #[cfg(feature = "extensions-draft")]
1683 application_export_tree: None,
1684 #[cfg(feature = "virtual-clients-draft")]
1685 emulation_group: false,
1686 };
1687 mls_group
1688 .store(provider.storage())
1689 .await
1690 .map_err(Error::StorageError)?;
1691 mls_group
1692 .store_epoch_keypairs(provider.storage(), &[leaf_keypair])
1693 .await
1694 .map_err(Error::StorageError)?;
1695
1696 Ok(mls_group)
1697 }
1698}
1699
1700#[cfg(feature = "virtual-clients-draft")]
1714#[derive(Debug, Default)]
1715pub struct VcExternalCommitJoinBuilder {
1716 join_config: MlsGroupJoinConfig,
1717 ratchet_tree: Option<RatchetTreeIn>,
1718 lifetime_policy: LeafNodeLifetimePolicy,
1719}
1720
1721#[cfg(feature = "virtual-clients-draft")]
1722impl VcExternalCommitJoinBuilder {
1723 pub fn new() -> Self {
1725 Self::default()
1726 }
1727
1728 pub fn with_config(mut self, join_config: MlsGroupJoinConfig) -> Self {
1730 self.join_config = join_config;
1731 self
1732 }
1733
1734 pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
1739 self.ratchet_tree = Some(ratchet_tree);
1740 self
1741 }
1742
1743 pub fn skip_lifetime_validation(mut self) -> Self {
1748 self.lifetime_policy = LeafNodeLifetimePolicy::Skip;
1749 self
1750 }
1751
1752 #[openmls_traits::maybe_async]
1764 pub async fn process_commit<Provider: OpenMlsProvider>(
1765 self,
1766 provider: &Provider,
1767 verifiable_group_info: VerifiableGroupInfo,
1768 external_commit: impl Into<ProtocolMessage>,
1769 epoch_id: EpochId,
1770 ) -> Result<StagedVcExternalCommitJoin, VcExternalCommitJoinError<Provider::StorageError>> {
1771 type Error<S> = VcExternalCommitJoinError<S>;
1772
1773 let Self {
1774 join_config,
1775 ratchet_tree,
1776 lifetime_policy,
1777 } = self;
1778
1779 let ratchet_tree = match verifiable_group_info.extensions().ratchet_tree() {
1782 Some(extension) => extension.ratchet_tree().clone(),
1783 None => ratchet_tree.ok_or(Error::MissingRatchetTree)?,
1784 };
1785 let (public_group, _group_info) = PublicGroup::from_ratchet_tree(
1786 provider.crypto(),
1787 ratchet_tree,
1788 verifiable_group_info,
1789 ProposalStore::new(),
1790 lifetime_policy,
1791 )?;
1792
1793 let ciphersuite = public_group.ciphersuite();
1800 let serialized_group_context = public_group
1801 .group_context()
1802 .tls_serialize_detached()
1803 .map_err(LibraryError::missing_bound_check)?;
1804 let own_leaf_index = public_group.leftmost_free_index(std::iter::empty())?;
1805 let init_secret = InitSecret::random(ciphersuite, provider.rand())
1806 .map_err(LibraryError::unexpected_crypto_error)?;
1807 let epoch_secrets =
1808 EpochSecrets::with_init_secret(provider.crypto(), ciphersuite, init_secret)
1809 .map_err(LibraryError::unexpected_crypto_error)?;
1810 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
1811 serialized_group_context,
1812 public_group.tree_size(),
1813 LeafNodeIndex::new(0u32),
1814 );
1815 let message_secrets_store = MessageSecretsStore::new_with_secret(
1818 &PastEpochDeletionPolicy::MaxEpochs(0),
1819 message_secrets,
1820 );
1821 let resumption_psk_store = ResumptionPskStore::new(join_config.number_of_resumption_psks);
1822 let mut group = MlsGroup {
1823 mls_group_config: join_config,
1824 own_leaf_nodes: vec![],
1825 aad: vec![],
1826 #[cfg(feature = "extensions-draft")]
1827 safe_aad: SafeAad::empty(),
1828 group_state: MlsGroupState::Operational,
1829 public_group,
1830 group_epoch_secrets,
1831 own_leaf_index,
1832 message_secrets_store,
1833 resumption_psk_store,
1834 #[cfg(feature = "extensions-draft")]
1835 application_export_tree: None,
1836 emulation_group: false,
1839 };
1840
1841 let processing::UnprotectedMessage::Unverified(unverified) =
1844 group.unprotect_message(provider, external_commit).await?
1845 else {
1846 return Err(Error::NotAnExternalCommit);
1847 };
1848 let verified = unverified
1849 .verify(group.ciphersuite(), provider.crypto(), group.version())
1850 .map_err(ProcessMessageError::from)?;
1851 if !matches!(verified.content.sender(), Sender::NewMemberCommit) {
1852 return Err(Error::NotAnExternalCommit);
1853 }
1854 let content = verified.content;
1855 let FramedContentBody::Commit(commit) = content.content() else {
1856 return Err(Error::NotAnExternalCommit);
1857 };
1858
1859 let derivation_info = commit
1864 .path
1865 .as_ref()
1866 .map(|path| path.leaf_node().vc_derivation_info())
1867 .transpose()?
1868 .flatten()
1869 .ok_or(Error::MissingDerivationInfo)?;
1870 if derivation_info.epoch_id() != &epoch_id {
1871 return Err(Error::EpochIdMismatch);
1872 }
1873
1874 let app_data_update_proposals =
1880 committed_app_data_update_proposals(commit, group.proposal_store());
1881
1882 Ok(StagedVcExternalCommitJoin {
1883 group,
1884 content,
1885 app_data_update_proposals,
1886 app_data_updates: None,
1887 })
1888 }
1889}
1890
1891#[cfg(feature = "virtual-clients-draft")]
1907pub struct StagedVcExternalCommitJoin {
1908 group: MlsGroup,
1910 content: AuthenticatedContent,
1912 app_data_update_proposals: Vec<AppDataUpdateProposal>,
1915 app_data_updates: Option<AppDataUpdates>,
1918}
1919
1920#[cfg(feature = "virtual-clients-draft")]
1921impl StagedVcExternalCommitJoin {
1922 pub fn prior_group_context(&self) -> &GroupContext {
1927 self.group.context()
1928 }
1929
1930 pub fn prior_members(&self) -> impl Iterator<Item = Member> + '_ {
1935 self.group.members()
1936 }
1937
1938 pub fn app_data_update_proposals(&self) -> impl Iterator<Item = &AppDataUpdateProposal> {
1942 self.app_data_update_proposals.iter()
1943 }
1944
1945 pub fn app_ephemeral_proposals_for_component_id(
1954 &self,
1955 component_id: ComponentId,
1956 ) -> impl Iterator<Item = &AppEphemeralProposal> {
1957 let proposals = match self.content.content() {
1958 FramedContentBody::Commit(commit) => commit.proposals.as_slice(),
1959 _ => &[],
1961 };
1962 proposals
1963 .iter()
1964 .filter_map(move |proposal_or_ref| match proposal_or_ref {
1965 ProposalOrRef::Proposal(proposal) => match proposal.as_ref() {
1966 Proposal::AppEphemeral(app_ephemeral)
1967 if app_ephemeral.component_id() == component_id =>
1968 {
1969 Some(app_ephemeral.as_ref())
1970 }
1971 _ => None,
1972 },
1973 ProposalOrRef::Reference(_) => None,
1974 })
1975 }
1976
1977 pub fn app_data_dictionary_updater(&self) -> AppDataDictionaryUpdater<'_> {
1980 AppDataDictionaryUpdater::new(self.group.context().app_data_dict())
1981 }
1982
1983 pub fn with_app_data_dictionary_updates(&mut self, app_data_updates: Option<AppDataUpdates>) {
1987 self.app_data_updates = app_data_updates;
1988 }
1989
1990 #[openmls_traits::maybe_async]
1999 pub async fn into_group<Provider: OpenMlsProvider>(
2000 self,
2001 provider: &Provider,
2002 ) -> Result<MlsGroup, VcExternalCommitJoinError<Provider::StorageError>> {
2003 type Error<S> = VcExternalCommitJoinError<S>;
2004
2005 let Self {
2006 mut group,
2007 content,
2008 app_data_update_proposals,
2009 app_data_updates,
2010 } = self;
2011
2012 if !app_data_update_proposals.is_empty() && app_data_updates.is_none() {
2018 return Err(StageCommitError::ApplyAppDataUpdateError(
2019 ApplyAppDataUpdateError::MissingAppDataUpdates,
2020 )
2021 .into());
2022 }
2023
2024 let FramedContentBody::Commit(commit) = content.content() else {
2025 return Err(LibraryError::custom("staged join without commit content").into());
2027 };
2028
2029 let material = group
2033 .load_vc_commit_material(provider, commit)
2034 .await?
2035 .ok_or(Error::MissingDerivationInfo)?;
2036 let staged = group
2037 .stage_commit_with_app_data_updates(
2038 &content,
2039 vec![],
2040 vec![],
2041 app_data_updates,
2042 provider,
2043 Some(material),
2044 )
2045 .await?;
2046 group.merge_staged_commit(provider, staged).await?;
2047 let deletion_policy = group.mls_group_config.past_epoch_deletion_policy().clone();
2048 group.resize_message_secrets_store(&deletion_policy);
2049 group
2050 .store(provider.storage())
2051 .await
2052 .map_err(Error::StorageError)?;
2053 Ok(group)
2054 }
2055}
2056
2057#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
2060pub enum LeafNodeLifetimePolicy {
2061 #[default]
2065 Verify,
2066
2067 Skip,
2069}
2070
2071pub struct JoinBuilder<'a, Provider: OpenMlsProvider> {
2075 provider: &'a Provider,
2076 processed_welcome: ProcessedWelcome,
2077 ratchet_tree: Option<RatchetTreeIn>,
2078 validate_lifetimes: LeafNodeLifetimePolicy,
2079 replace_old_group: bool,
2080 branch: Option<BranchInfo>,
2083 reinit: Option<ReInitInfo>,
2086 check_members: bool,
2090}
2091
2092impl<'a, Provider: OpenMlsProvider> JoinBuilder<'a, Provider> {
2093 pub fn new(provider: &'a Provider, processed_welcome: ProcessedWelcome) -> Self {
2095 Self {
2096 provider,
2097 processed_welcome,
2098 ratchet_tree: None,
2099 replace_old_group: false,
2100 validate_lifetimes: LeafNodeLifetimePolicy::Verify,
2101 branch: None,
2102 reinit: None,
2103 check_members: true,
2104 }
2105 }
2106
2107 fn with_branch_info(mut self, branch_info: BranchInfo) -> Self {
2110 self.branch = Some(branch_info);
2111 self
2112 }
2113
2114 fn with_reinit_info(mut self, reinit_info: ReInitInfo) -> Self {
2117 self.reinit = Some(reinit_info);
2118 self
2119 }
2120
2121 pub fn check_members(mut self, check_members: bool) -> Self {
2135 self.check_members = check_members;
2136 self
2137 }
2138
2139 pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
2141 self.ratchet_tree = Some(ratchet_tree);
2142 self
2143 }
2144
2145 pub fn replace_old_group(mut self) -> Self {
2147 self.replace_old_group = true;
2148 self
2149 }
2150
2151 pub fn skip_lifetime_validation(mut self) -> Self {
2156 self.validate_lifetimes = LeafNodeLifetimePolicy::Skip;
2157 self
2158 }
2159
2160 pub fn processed_welcome(&self) -> &ProcessedWelcome {
2164 &self.processed_welcome
2165 }
2166
2167 #[openmls_traits::maybe_async]
2169 pub async fn build(self) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
2170 if let Some(branch_info) = &self.branch {
2174 let group_info = self.processed_welcome.unverified_group_info();
2175 if group_info.group_context().protocol_version() != branch_info.version()
2176 || group_info.ciphersuite() != branch_info.ciphersuite()
2177 {
2178 return Err(WelcomeError::SubgroupParameterMismatch);
2179 }
2180 if group_info.group_context().epoch().as_u64() != 1 {
2181 return Err(WelcomeError::SubgroupEpochInvalid);
2182 }
2183 }
2184 if let Some(reinit_info) = &self.reinit {
2185 let group_info = self.processed_welcome.unverified_group_info();
2188 let group_context = group_info.group_context();
2189 let reinit_proposal = reinit_info.proposal();
2190 if group_context.protocol_version() != reinit_proposal.version()
2193 || group_context.ciphersuite() != reinit_proposal.ciphersuite()
2194 || group_context.group_id() != reinit_proposal.group_id()
2195 || group_context.extensions() != reinit_proposal.extensions()
2196 {
2197 return Err(WelcomeError::ReInitParameterMismatch);
2198 }
2199 if group_context.epoch().as_u64() != 1 {
2201 return Err(WelcomeError::ReInitEpochInvalid);
2202 }
2203 }
2204
2205 let staged_welcome = self
2206 .processed_welcome
2207 .into_staged_welcome_inner(
2208 self.provider,
2209 self.ratchet_tree,
2210 self.validate_lifetimes,
2211 self.replace_old_group,
2212 )
2213 .await?;
2214
2215 if self.check_members {
2221 if let Some(branch_info) = &self.branch {
2222 for member in staged_welcome.members() {
2223 if !branch_info
2224 .member_credentials()
2225 .contains(&member.credential)
2226 {
2227 return Err(WelcomeError::SubgroupLeafMismatch);
2228 }
2229 }
2230 }
2231 if let Some(reinit_info) = &self.reinit {
2232 let old_credentials = reinit_info.member_credentials();
2233 let mut unseen_old_credentials = old_credentials.to_vec();
2234 for member in staged_welcome.members() {
2235 if let Some(position) = unseen_old_credentials
2236 .iter()
2237 .position(|c| *c == member.credential)
2238 {
2239 unseen_old_credentials.remove(position);
2240 } else {
2241 return Err(WelcomeError::ReInitLeafMismatch);
2242 }
2243 }
2244 if !unseen_old_credentials.is_empty() {
2246 return Err(WelcomeError::ReInitLeavesMissing(unseen_old_credentials));
2247 }
2248 }
2249 }
2250
2251 Ok(staged_welcome)
2252 }
2253}