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 messages::{
13 group_info::{GroupInfo, VerifiableGroupInfo},
14 Welcome,
15 },
16 schedule::{
17 psk::{store::ResumptionPskStore, PreSharedKeyId},
18 EpochSecretsResult,
19 },
20 storage::OpenMlsProvider,
21 treesync::{
22 errors::{DerivePathError, PublicTreeError},
23 node::leaf_node::{Capabilities, LeafNodeParameters},
24 RatchetTreeIn,
25 },
26};
27
28use crate::key_packages::KeyPackage;
29
30#[cfg(feature = "virtual-clients-draft")]
31use crate::{
32 component::ComponentId,
33 framing::mls_auth_content::AuthenticatedContent,
34 group::mls_group::processing::{AppDataDictionaryUpdater, AppDataUpdates},
35 messages::proposals::{AppDataUpdateProposal, AppEphemeralProposal},
36};
37
38impl MlsGroup {
39 pub fn builder() -> MlsGroupBuilder {
44 MlsGroupBuilder::new()
45 }
46
47 pub fn new<Provider: OpenMlsProvider>(
50 provider: &Provider,
51 signer: &impl Signer,
52 mls_group_create_config: &MlsGroupCreateConfig,
53 credential_with_key: CredentialWithKey,
54 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
55 MlsGroupBuilder::new().build_internal(
56 provider,
57 signer,
58 credential_with_key,
59 Some(mls_group_create_config.clone()),
60 )
61 }
62
63 pub fn new_with_group_id<Provider: OpenMlsProvider>(
66 provider: &Provider,
67 signer: &impl Signer,
68 mls_group_create_config: &MlsGroupCreateConfig,
69 group_id: GroupId,
70 credential_with_key: CredentialWithKey,
71 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
72 MlsGroupBuilder::new()
73 .with_group_id(group_id)
74 .build_internal(
75 provider,
76 signer,
77 credential_with_key,
78 Some(mls_group_create_config.clone()),
79 )
80 }
81
82 #[allow(clippy::too_many_arguments)]
97 #[deprecated(
98 since = "0.7.1",
99 note = "Use the `MlsGroup::external_commit_builder` instead."
100 )]
101 pub fn join_by_external_commit<Provider: OpenMlsProvider>(
102 provider: &Provider,
103 signer: &impl Signer,
104 ratchet_tree: Option<RatchetTreeIn>,
105 verifiable_group_info: VerifiableGroupInfo,
106 mls_group_config: &MlsGroupJoinConfig,
107 capabilities: Option<Capabilities>,
108 extensions: Option<Extensions<LeafNode>>,
109 aad: &[u8],
110 credential_with_key: CredentialWithKey,
111 ) -> Result<(Self, MlsMessageOut, Option<GroupInfo>), ExternalCommitError<Provider::StorageError>>
112 {
113 let leaf_node_parameters = LeafNodeParameters::builder()
114 .with_capabilities(capabilities.unwrap_or_default())
115 .with_extensions(extensions.unwrap_or_default())
116 .build();
117
118 let mut external_commit_builder = ExternalCommitBuilder::new()
119 .with_aad(aad.to_vec())
120 .with_config(mls_group_config.clone());
121
122 if let Some(ratchet_tree) = ratchet_tree {
123 external_commit_builder = external_commit_builder.with_ratchet_tree(ratchet_tree)
124 }
125
126 let (mls_group, commit_message_bundle) = external_commit_builder
127 .build_group(provider, verifiable_group_info, credential_with_key)?
128 .leaf_node_parameters(leaf_node_parameters)
129 .load_psks(provider.storage())
130 .map_err(|e| {
131 log::error!("Error loading PSKs for external commit: {e:?}");
132 LibraryError::custom("Error loading PSKs for external commit")
133 })?
134 .build(provider.rand(), provider.crypto(), signer, |_| true)?
135 .finalize(provider)?;
136
137 let (commit, _, group_info) = commit_message_bundle.into_contents();
138
139 Ok((mls_group, commit, group_info))
140 }
141}
142
143impl ProcessedWelcome {
144 pub fn new_from_welcome<Provider: OpenMlsProvider>(
151 provider: &Provider,
152 mls_group_config: &MlsGroupJoinConfig,
153 welcome: Welcome,
154 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
155 let ciphersuite = welcome.ciphersuite();
156 provider
159 .crypto()
160 .supports(ciphersuite)
161 .map_err(|_| WelcomeError::UnsupportedCiphersuite(ciphersuite))?;
162
163 let (resumption_psk_store, key_material) =
164 keys_for_welcome(mls_group_config, &welcome, provider)?;
165
166 let Some(egs) =
167 welcome.find_encrypted_group_secret(key_material.key_package_ref(provider.crypto())?)
168 else {
169 return Err(WelcomeError::JoinerSecretNotFound);
170 };
171
172 if let Some(key_package_bundle) = key_material.key_package_bundle() {
176 if welcome.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
177 let e = WelcomeError::CiphersuiteMismatch;
178 log::debug!("new_from_welcome {e:?}");
179 return Err(e);
180 }
181 }
182
183 let group_secrets = GroupSecrets::try_from_ciphertext(
184 key_material.init_private_key(),
185 egs.encrypted_group_secrets(),
186 welcome.encrypted_group_info(),
187 ciphersuite,
188 provider.crypto(),
189 )?;
190
191 PreSharedKeyId::validate_in_welcome(&group_secrets.psks, ciphersuite)?;
193
194 let psk_secret = {
195 let psks = load_psks(
196 provider.storage(),
197 &resumption_psk_store,
198 &group_secrets.psks,
199 )?;
200
201 PskSecret::new(provider.crypto(), ciphersuite, psks)?
202 };
203
204 let mut key_schedule = KeySchedule::init(
206 ciphersuite,
207 provider.crypto(),
208 &group_secrets.joiner_secret,
209 psk_secret,
210 )?;
211
212 let (welcome_key, welcome_nonce) = key_schedule
214 .welcome(provider.crypto(), ciphersuite)
215 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?
216 .derive_welcome_key_nonce(provider.crypto(), ciphersuite)
217 .map_err(LibraryError::unexpected_crypto_error)?;
218
219 let verifiable_group_info = VerifiableGroupInfo::try_from_ciphertext(
220 &welcome_key,
221 &welcome_nonce,
222 welcome.encrypted_group_info(),
223 &[],
224 provider.crypto(),
225 )?;
226
227 let serialized_group_context = verifiable_group_info
228 .group_context()
229 .tls_serialize_detached()
230 .map_err(LibraryError::missing_bound_check)?;
231
232 key_schedule.add_context(provider.crypto(), &serialized_group_context)?;
234
235 let epoch_secrets = key_schedule.epoch_secrets(provider.crypto(), ciphersuite)?;
236
237 if let Some(key_package_bundle) = key_material.key_package_bundle() {
242 if let Some(required_capabilities) =
243 verifiable_group_info.extensions().required_capabilities()
244 {
245 key_package_bundle
248 .key_package()
249 .leaf_node()
250 .capabilities()
251 .supports_required_capabilities(required_capabilities)?;
252 }
253
254 if verifiable_group_info.ciphersuite() != key_package_bundle.key_package().ciphersuite()
258 {
259 let e = WelcomeError::CiphersuiteMismatch;
260 log::debug!("new_from_welcome {e:?}");
261 return Err(e);
262 }
263 }
264
265 Ok(Self {
266 mls_group_config: mls_group_config.clone(),
267 ciphersuite,
268 group_secrets,
269 epoch_secrets,
270 verifiable_group_info,
271 resumption_psk_store,
272 key_material,
273 })
274 }
275
276 pub fn unverified_group_info(&self) -> &VerifiableGroupInfo {
280 &self.verifiable_group_info
281 }
282
283 pub fn psks(&self) -> &[PreSharedKeyId] {
287 &self.group_secrets.psks
288 }
289
290 pub fn into_staged_welcome<Provider: OpenMlsProvider>(
293 self,
294 provider: &Provider,
295 ratchet_tree: Option<RatchetTreeIn>,
296 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
297 self.into_staged_welcome_inner(
298 provider,
299 ratchet_tree,
300 LeafNodeLifetimePolicy::Verify,
301 false,
302 )
303 }
304
305 pub(crate) fn into_staged_welcome_inner<Provider: OpenMlsProvider>(
308 mut self,
309 provider: &Provider,
310 ratchet_tree: Option<RatchetTreeIn>,
311 validate_lifetimes: LeafNodeLifetimePolicy,
312 replace_old_group: bool,
313 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
314 if !replace_old_group
316 && MlsGroup::load(provider.storage(), self.verifiable_group_info.group_id())
317 .map_err(WelcomeError::StorageError)?
318 .is_some()
319 {
320 return Err(WelcomeError::GroupAlreadyExists);
321 }
322
323 let ratchet_tree = match self.verifiable_group_info.extensions().ratchet_tree() {
330 Some(extension) => extension.ratchet_tree().clone(),
331 None => match ratchet_tree {
332 Some(ratchet_tree) => ratchet_tree,
333 None => return Err(WelcomeError::MissingRatchetTree),
334 },
335 };
336
337 let (public_group, _group_info_extensions) = PublicGroup::from_ratchet_tree(
340 provider.crypto(),
341 ratchet_tree,
342 self.verifiable_group_info.clone(),
343 ProposalStore::new(),
344 validate_lifetimes,
345 )?;
346
347 let own_leaf_index = match &self.key_material.inner() {
353 WelcomeKeyMaterialInner::KeyPackage(key_package_bundle) => {
354 let added_leaf_supports_all_group_context_extensions = public_group
358 .group_context()
359 .extensions()
360 .iter()
361 .all(|extension| {
362 key_package_bundle
363 .key_package
364 .leaf_node()
365 .supports_extension(&extension.extension_type())
366 });
367 if !added_leaf_supports_all_group_context_extensions {
368 return Err(WelcomeError::UnsupportedExtensions);
369 }
370
371 public_group
372 .members()
373 .find_map(|m| {
374 if m.signature_key
375 == key_package_bundle
376 .key_package()
377 .leaf_node()
378 .signature_key()
379 .as_slice()
380 {
381 Some(m.index)
382 } else {
383 None
384 }
385 })
386 .ok_or(WelcomeError::PublicTreeError(
387 PublicTreeError::MalformedTree,
388 ))?
389 }
390 #[cfg(feature = "virtual-clients-draft")]
391 WelcomeKeyMaterialInner::VirtualClient(material) => {
392 find_and_validate_vc_own_leaf(provider, &public_group, material)?
393 }
394 };
395
396 struct KeyScheduleResult {
397 group_epoch_secrets: GroupEpochSecrets,
398 message_secrets: MessageSecrets,
399 #[cfg(feature = "extensions-draft")]
400 application_exporter: ApplicationExportSecret,
401 }
402 let KeyScheduleResult {
403 group_epoch_secrets,
404 message_secrets,
405 #[cfg(feature = "extensions-draft")]
406 application_exporter: application_export_secret,
407 } = {
408 let serialized_group_context = public_group
409 .group_context()
410 .tls_serialize_detached()
411 .map_err(LibraryError::missing_bound_check)?;
412
413 let EpochSecretsResult {
414 epoch_secrets,
415 #[cfg(feature = "extensions-draft")]
416 application_exporter,
417 } = self.epoch_secrets;
418
419 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
420 serialized_group_context,
421 public_group.tree_size(),
422 own_leaf_index,
423 );
424
425 KeyScheduleResult {
426 group_epoch_secrets,
427 message_secrets,
428 #[cfg(feature = "extensions-draft")]
429 application_exporter,
430 }
431 };
432
433 let confirmation_tag = message_secrets
434 .confirmation_key()
435 .tag(
436 provider.crypto(),
437 self.ciphersuite,
438 public_group.group_context().confirmed_transcript_hash(),
439 )
440 .map_err(LibraryError::unexpected_crypto_error)?;
441
442 if &confirmation_tag != public_group.confirmation_tag() {
445 log::error!("Confirmation tag mismatch");
446 log_crypto!(trace, " Got: {:x?}", confirmation_tag);
447 log_crypto!(trace, " Expected: {:x?}", public_group.confirmation_tag());
448
449 if !crate::skip_validation::is_disabled::confirmation_tag() {
452 return Err(WelcomeError::ConfirmationTagMismatch);
453 }
454 }
455
456 let message_secrets_store = MessageSecretsStore::new_with_secret(
457 &PastEpochDeletionPolicy::MaxEpochs(0),
458 message_secrets,
459 );
460
461 let resumption_psk = group_epoch_secrets.resumption_psk();
463 self.resumption_psk_store
464 .add(public_group.group_context().epoch(), resumption_psk.clone());
465
466 let welcome_sender_index = self.verifiable_group_info.signer();
467 let path_keypairs = if let Some(path_secret) = self.group_secrets.path_secret {
468 let (path_keypairs, _commit_secret) = public_group
469 .derive_path_secrets(
470 provider.crypto(),
471 self.ciphersuite,
472 path_secret,
473 welcome_sender_index,
474 own_leaf_index,
475 )
476 .map_err(|e| match e {
477 DerivePathError::LibraryError(e) => e.into(),
478 DerivePathError::PublicKeyMismatch => {
479 WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch)
480 }
481 })?;
482 Some(path_keypairs)
483 } else {
484 None
485 };
486
487 let staged_welcome = StagedWelcome {
488 mls_group_config: self.mls_group_config,
489 public_group,
490 group_epoch_secrets,
491 own_leaf_index,
492 message_secrets_store,
493 #[cfg(feature = "extensions-draft")]
494 application_export_secret,
495 resumption_psk_store: self.resumption_psk_store,
496 verifiable_group_info: self.verifiable_group_info,
497 key_material: self.key_material,
498 path_keypairs,
499 #[cfg(feature = "virtual-clients-draft")]
500 emulation_group: false,
501 };
502
503 Ok(staged_welcome)
504 }
505
506 pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
511 &self,
512 crypto: &CryptoProvider,
513 label: &str,
514 context: &[u8],
515 key_length: usize,
516 ) -> Result<Vec<u8>, ExportSecretError> {
517 if key_length > u16::MAX as usize {
518 log::error!("Got a key that is larger than u16::MAX");
519 return Err(ExportSecretError::KeyLengthTooLong);
520 }
521
522 Ok(self
523 .epoch_secrets
524 .epoch_secrets
525 .exporter_secret()
526 .derive_exported_secret(self.ciphersuite, crypto, label, context, key_length)
527 .map_err(LibraryError::unexpected_crypto_error)?)
528 }
529
530 pub fn own_key_package(&self) -> Option<&KeyPackage> {
533 self.key_material
534 .key_package_bundle()
535 .map(|bundle| bundle.key_package())
536 }
537}
538
539impl StagedWelcome {
540 pub fn new_from_welcome<Provider: OpenMlsProvider>(
548 provider: &Provider,
549 mls_group_config: &MlsGroupJoinConfig,
550 welcome: Welcome,
551 ratchet_tree: Option<RatchetTreeIn>,
552 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
553 let processed_welcome =
554 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
555
556 processed_welcome.into_staged_welcome(provider, ratchet_tree)
557 }
558
559 pub fn build_from_welcome<'a, Provider: OpenMlsProvider>(
564 provider: &'a Provider,
565 mls_group_config: &MlsGroupJoinConfig,
566 welcome: Welcome,
567 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
569 let processed_welcome =
570 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
571
572 Ok(JoinBuilder::new(provider, processed_welcome))
574 }
575
576 pub fn welcome_sender_index(&self) -> LeafNodeIndex {
580 self.verifiable_group_info.signer()
581 }
582
583 pub fn welcome_sender(&self) -> Result<&LeafNode, LibraryError> {
587 let sender_index = self.welcome_sender_index();
588 self.public_group
589 .leaf(sender_index)
590 .ok_or_else(|| LibraryError::custom("no leaf with given welcome sender index exists"))
591 }
592
593 pub fn own_leaf_index(&self) -> LeafNodeIndex {
595 self.own_leaf_index
596 }
597
598 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
600 self.public_group.leaf(self.own_leaf_index())
601 }
602
603 pub fn group_context(&self) -> &GroupContext {
605 self.public_group.group_context()
606 }
607
608 pub fn members(&self) -> impl Iterator<Item = Member> + '_ {
610 self.public_group.members()
611 }
612
613 #[cfg(feature = "extensions-draft")]
615 pub fn application_export_secret(&self) -> &ApplicationExportSecret {
616 &self.application_export_secret
617 }
618
619 #[cfg(feature = "virtual-clients-draft")]
629 pub fn emulation_group(mut self, emulation_group: bool) -> Self {
630 self.emulation_group = emulation_group;
631 self
632 }
633
634 pub fn into_group<Provider: OpenMlsProvider>(
636 self,
637 provider: &Provider,
638 ) -> Result<MlsGroup, WelcomeError<Provider::StorageError>> {
639 let group_keypairs = if let Some(path_keypairs) = self.path_keypairs {
642 let mut keypairs = vec![self.key_material.encryption_key_pair()];
643 keypairs.extend_from_slice(&path_keypairs);
644 keypairs
645 } else {
646 vec![self.key_material.encryption_key_pair()]
647 };
648
649 #[cfg(feature = "extensions-draft")]
650 #[cfg_attr(not(feature = "virtual-clients-draft"), allow(unused_mut))]
651 let mut application_export_tree =
652 ApplicationExportTree::new(self.application_export_secret);
653
654 #[cfg(feature = "virtual-clients-draft")]
657 if self.emulation_group {
658 crate::components::vc_derivation_info::register_vc_derivation_epoch(
659 provider.crypto(),
660 provider.storage(),
661 Some(&mut application_export_tree),
662 crate::components::vc_derivation_info::VcDerivationEpochParams::for_public_group(
663 &self.public_group,
664 self.own_leaf_index,
665 ),
666 )?;
667 }
668
669 let past_epoch_deletion_policy = self.mls_group_config.past_epoch_deletion_policy().clone();
670
671 let mut mls_group = MlsGroup {
672 mls_group_config: self.mls_group_config,
673 own_leaf_nodes: vec![],
674 aad: vec![],
675 #[cfg(feature = "extensions-draft")]
676 safe_aad: crate::framing::SafeAad::empty(),
677 group_state: MlsGroupState::Operational,
678 public_group: self.public_group,
679 group_epoch_secrets: self.group_epoch_secrets,
680 own_leaf_index: self.own_leaf_index,
681 message_secrets_store: self.message_secrets_store,
682 resumption_psk_store: self.resumption_psk_store,
683 #[cfg(feature = "extensions-draft")]
684 application_export_tree: Some(application_export_tree),
685 #[cfg(feature = "virtual-clients-draft")]
686 emulation_group: self.emulation_group,
687 };
688
689 mls_group
690 .store_epoch_keypairs(provider.storage(), group_keypairs.as_slice())
691 .map_err(WelcomeError::StorageError)?;
692 mls_group.resize_message_secrets_store(&past_epoch_deletion_policy);
694
695 mls_group
696 .store(provider.storage())
697 .map_err(WelcomeError::StorageError)?;
698
699 Ok(mls_group)
700 }
701
702 pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
707 &self,
708 crypto: &CryptoProvider,
709 label: &str,
710 context: &[u8],
711 key_length: usize,
712 ) -> Result<Vec<u8>, ExportSecretError> {
713 if key_length > u16::MAX as usize {
714 log::error!("Got a key that is larger than u16::MAX");
715 return Err(ExportSecretError::KeyLengthTooLong);
716 }
717
718 Ok(self
719 .group_epoch_secrets
720 .exporter_secret()
721 .derive_exported_secret(
722 self.group_context().ciphersuite(),
723 crypto,
724 label,
725 context,
726 key_length,
727 )
728 .map_err(LibraryError::unexpected_crypto_error)?)
729 }
730}
731
732fn keys_for_welcome<Provider: OpenMlsProvider>(
733 mls_group_config: &MlsGroupJoinConfig,
734 welcome: &Welcome,
735 provider: &Provider,
736) -> Result<
737 (ResumptionPskStore, WelcomeKeyMaterial),
738 WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
739> {
740 let resumption_psk_store = ResumptionPskStore::new(mls_group_config.number_of_resumption_psks);
741
742 for egs in welcome.secrets() {
743 let hash_ref = egs.new_member();
744 if let Some(key_package_bundle) = provider
745 .storage()
746 .key_package(&hash_ref)
747 .map_err(WelcomeError::StorageError)?
748 {
749 let key_package_bundle: KeyPackageBundle = key_package_bundle;
750 if !key_package_bundle.key_package().last_resort() {
751 provider
752 .storage()
753 .delete_key_package(
754 &key_package_bundle.key_package.hash_ref(provider.crypto())?,
755 )
756 .map_err(WelcomeError::StorageError)?;
757 } else {
758 log::debug!("Key package has last resort extension, not deleting");
759 }
760 return Ok((
761 resumption_psk_store,
762 WelcomeKeyMaterial::with_key_package_bundle(key_package_bundle),
763 ));
764 }
765
766 #[cfg(feature = "virtual-clients-draft")]
767 if let Some(material) =
768 resolve_vc_welcome_material(provider, welcome.ciphersuite(), &hash_ref)?
769 {
770 provider
771 .storage()
772 .delete_retained_key_package_material(&hash_ref)
773 .map_err(|e| {
774 use crate::components::vc_derivation_info::VirtualClientsError;
775
776 log::error!(
777 "vc: delete retained key package material in welcome failed: {e:?}"
778 );
779 VirtualClientsError::StorageError
780 })?;
781 return Ok((
782 resumption_psk_store,
783 WelcomeKeyMaterial::with_vc_welcome_material(material),
784 ));
785 }
786 }
787
788 Err(WelcomeError::NoMatchingKeyPackage)
789}
790
791#[cfg(feature = "virtual-clients-draft")]
804pub(crate) fn resolve_vc_welcome_material<Provider: OpenMlsProvider>(
805 provider: &Provider,
806 ciphersuite: Ciphersuite,
807 hash_ref: &crate::ciphersuite::hash_ref::KeyPackageRef,
808) -> Result<
809 Option<crate::components::vc_derivation_info::VcWelcomeMaterial>,
810 WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
811> {
812 use crate::components::vc_derivation_info::{RetainedKeyPackageMaterial, VcWelcomeMaterial};
813
814 let storage = provider.storage();
815 let Some(material) = storage
816 .retained_key_package_material::<_, RetainedKeyPackageMaterial>(hash_ref)
817 .map_err(WelcomeError::StorageError)?
818 else {
819 return Ok(None);
820 };
821
822 if material.key_package_ciphersuite != ciphersuite {
823 return Err(WelcomeError::CiphersuiteMismatch);
824 }
825
826 let crypto = provider.crypto();
830 let init_key_pair = material
831 .key_package_seed_secret
832 .derive_init_key_secret(crypto, ciphersuite)?
833 .generate_init_key_pair(crypto, ciphersuite)?;
834 let encryption_keypair = material
835 .key_package_seed_secret
836 .derive_encryption_key_secret(crypto, ciphersuite)?
837 .generate_encryption_key_pair(crypto, ciphersuite)?;
838
839 Ok(Some(VcWelcomeMaterial {
840 key_package_ref: hash_ref.clone(),
841 epoch_id: material.epoch_id,
842 leaf_index: material.leaf_index,
843 generation: material.generation,
844 key_package_index: material.key_package_index,
845 init_private_key: init_key_pair.private,
846 init_key: init_key_pair.public.into(),
847 encryption_keypair,
848 }))
849}
850
851#[cfg(feature = "virtual-clients-draft")]
864fn find_and_validate_vc_own_leaf<Provider: OpenMlsProvider>(
865 provider: &Provider,
866 public_group: &PublicGroup,
867 material: &crate::components::vc_derivation_info::VcWelcomeMaterial,
868) -> Result<LeafNodeIndex, WelcomeError<<Provider as OpenMlsProvider>::StorageError>> {
869 use tls_codec::{DeserializeBytes as _, Serialize as _};
870
871 use crate::components::vc_derivation_info::{
872 DerivationInfo, DerivationInfoTbe, VcDerivationEpochState, VirtualClientOperationType,
873 VirtualClientsError, VC_COMPONENT_ID,
874 };
875
876 let crypto = provider.crypto();
877 let derived_encryption_key = material.encryption_keypair.public_key().as_slice().to_vec();
878
879 let own_index = public_group
880 .members()
881 .find(|m| m.encryption_key == derived_encryption_key)
882 .map(|m| m.index)
883 .ok_or(WelcomeError::PublicTreeError(
884 PublicTreeError::MalformedTree,
885 ))?;
886
887 let own_leaf = public_group
888 .leaf(own_index)
889 .ok_or(WelcomeError::PublicTreeError(
890 PublicTreeError::MalformedTree,
891 ))?;
892
893 let derivation_info_bytes = own_leaf
894 .extensions()
895 .app_data_dictionary()
896 .and_then(|dict| dict.dictionary().get(&VC_COMPONENT_ID))
897 .ok_or(VirtualClientsError::VcComponentNotListed)?;
898 let derivation_info = DerivationInfo::tls_deserialize_exact_bytes(derivation_info_bytes)
899 .map_err(|e| {
900 log::error!("vc: welcome leaf derivation info deserialize failed: {e:?}");
901 VirtualClientsError::DerivationInfoMalformed
902 })?;
903 if derivation_info.epoch_id() != &material.epoch_id {
904 log::error!("vc: welcome leaf epoch id does not match the retained material");
905 return Err(VirtualClientsError::DerivationInfoMalformed.into());
906 }
907
908 let state: VcDerivationEpochState = provider
909 .storage()
910 .vc_derivation_epoch_state(&material.epoch_id)
911 .map_err(|e| {
912 log::error!("vc: load derivation epoch state in welcome staging failed: {e:?}");
913 VirtualClientsError::StorageError
914 })?
915 .ok_or(VirtualClientsError::MissingDerivationEpochState)?;
916 let (_state_leaf_index, epoch_encryption_key, emulation_ciphersuite) = state.into_parts();
917
918 let leaf_encryption_key = own_leaf
919 .encryption_key()
920 .tls_serialize_detached()
921 .map_err(VirtualClientsError::from)?;
922 let tbe = derivation_info.decrypt(
925 crypto,
926 emulation_ciphersuite,
927 &epoch_encryption_key,
928 &leaf_encryption_key,
929 VirtualClientOperationType::KeyPackage,
930 )?;
931
932 let DerivationInfoTbe::KeyPackage {
933 leaf_index,
934 generation,
935 key_package_index,
936 } = tbe
937 else {
938 log::error!("vc: welcome leaf derivation info is not a key-package variant");
939 return Err(VirtualClientsError::DerivationInfoMalformed.into());
940 };
941 if leaf_index != material.leaf_index
942 || generation != material.generation
943 || key_package_index != material.key_package_index
944 {
945 log::error!("vc: welcome leaf derivation info does not match the retained material");
946 return Err(VirtualClientsError::DerivationInfoMalformed.into());
947 }
948
949 Ok(own_index)
950}
951
952#[cfg(feature = "virtual-clients-draft")]
953impl MlsGroup {
954 pub fn vc_external_commit_join_builder() -> VcExternalCommitJoinBuilder {
958 VcExternalCommitJoinBuilder::new()
959 }
960
961 pub fn vc_join_at_creation<Provider: OpenMlsProvider>(
977 provider: &Provider,
978 join_config: &MlsGroupJoinConfig,
979 verifiable_group_info: VerifiableGroupInfo,
980 ratchet_tree: Option<RatchetTreeIn>,
981 epoch_id: crate::components::vc_derivation_info::EpochId,
982 ) -> Result<MlsGroup, crate::group::errors::VcGroupCreationJoinError<Provider::StorageError>>
983 {
984 use tls_codec::{DeserializeBytes as _, Serialize as _};
985
986 use crate::{
987 components::vc_derivation_info::{
988 load_vc_epoch_state_and_tree, DerivationInfo, DerivationInfoTbe,
989 VirtualClientOperationType, VirtualClientsError, VC_COMPONENT_ID,
990 },
991 group::errors::VcGroupCreationJoinError as Error,
992 group::public_group::PublicGroup,
993 schedule::EpochSecrets,
994 treesync::node::leaf_node::LeafNodeSource,
995 };
996
997 let ratchet_tree = match verifiable_group_info.extensions().ratchet_tree() {
1000 Some(extension) => extension.ratchet_tree().clone(),
1001 None => ratchet_tree.ok_or(Error::MissingRatchetTree)?,
1002 };
1003 let (public_group, group_info) = PublicGroup::from_ratchet_tree(
1004 provider.crypto(),
1005 ratchet_tree,
1006 verifiable_group_info,
1007 ProposalStore::new(),
1008 LeafNodeLifetimePolicy::default(),
1009 )?;
1010 let ciphersuite = public_group.ciphersuite();
1011
1012 if public_group.members().count() != 1 {
1014 return Err(Error::NotASingleLeafTree);
1015 }
1016 let creator_index = LeafNodeIndex::new(0);
1017 let creator_leaf = public_group
1018 .leaf(creator_index)
1019 .ok_or(Error::NotASingleLeafTree)?;
1020
1021 let LeafNodeSource::KeyPackage(_) = creator_leaf.leaf_node_source() else {
1023 return Err(Error::CreatorLeafNotKeyPackageSourced);
1024 };
1025
1026 let derivation_info_bytes = creator_leaf
1028 .extensions()
1029 .app_data_dictionary()
1030 .and_then(|dict| dict.dictionary().get(&VC_COMPONENT_ID))
1031 .ok_or(Error::MissingDerivationInfo)?;
1032 let derivation_info = DerivationInfo::tls_deserialize_exact_bytes(derivation_info_bytes)
1033 .map_err(|_| VirtualClientsError::DerivationInfoMalformed)?;
1034 if derivation_info.epoch_id() != &epoch_id {
1035 return Err(Error::EpochIdMismatch);
1036 }
1037
1038 let (state, mut operation_tree) = load_vc_epoch_state_and_tree(provider, &epoch_id)?;
1040 let (_leaf_index, epoch_encryption_key, emulation_ciphersuite) = state.into_parts();
1041
1042 let leaf_encryption_key = creator_leaf
1045 .encryption_key()
1046 .tls_serialize_detached()
1047 .map_err(VirtualClientsError::from)?;
1048 let tbe = derivation_info.decrypt(
1049 provider.crypto(),
1050 emulation_ciphersuite,
1051 &epoch_encryption_key,
1052 &leaf_encryption_key,
1053 VirtualClientOperationType::KeyPackage,
1054 )?;
1055 let DerivationInfoTbe::KeyPackage {
1056 leaf_index,
1057 generation,
1058 key_package_index,
1059 } = tbe
1060 else {
1061 return Err(LibraryError::custom("unexpected derivation info variant").into());
1064 };
1065
1066 let operation_secret = operation_tree.derive_operation_secret(
1073 provider.crypto(),
1074 emulation_ciphersuite,
1075 &epoch_id,
1076 leaf_index,
1077 VirtualClientOperationType::KeyPackage,
1078 generation,
1079 b"",
1080 )?;
1081 let key_package_seed = operation_secret.derive_key_package_seed_secret(
1082 provider.crypto(),
1083 ciphersuite,
1084 key_package_index,
1085 )?;
1086 let leaf_keypair = key_package_seed
1087 .derive_encryption_key_secret(provider.crypto(), ciphersuite)?
1088 .generate_encryption_key_pair(provider.crypto(), ciphersuite)?;
1089 if leaf_keypair.public_key() != creator_leaf.encryption_key() {
1090 return Err(Error::LeafKeyMismatch);
1091 }
1092
1093 let serialized_group_context = public_group
1097 .group_context()
1098 .tls_serialize_detached()
1099 .map_err(LibraryError::missing_bound_check)?;
1100 let epoch_secret =
1101 key_package_seed.derive_group_creation_secret(provider.crypto(), ciphersuite)?;
1102 let epoch_secrets =
1103 EpochSecrets::from_epoch_secret(provider.crypto(), ciphersuite, epoch_secret)
1104 .map_err(LibraryError::unexpected_crypto_error)?;
1105 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
1106 serialized_group_context,
1107 public_group.tree_size(),
1108 creator_index,
1109 );
1110 let expected_confirmation_tag = message_secrets
1111 .confirmation_key()
1112 .tag(
1113 provider.crypto(),
1114 ciphersuite,
1115 public_group.group_context().confirmed_transcript_hash(),
1116 )
1117 .map_err(LibraryError::unexpected_crypto_error)?;
1118 if &expected_confirmation_tag != group_info.confirmation_tag() {
1119 return Err(Error::ConfirmationTagMismatch);
1120 }
1121
1122 provider
1125 .storage()
1126 .write_vc_operation_tree(&epoch_id, &operation_tree)
1127 .map_err(Error::StorageError)?;
1128
1129 let message_secrets_store = MessageSecretsStore::new_with_secret(
1130 join_config.past_epoch_deletion_policy(),
1131 message_secrets,
1132 );
1133 let mut resumption_psk_store =
1134 ResumptionPskStore::new(join_config.number_of_resumption_psks);
1135 resumption_psk_store.add(
1136 public_group.group_context().epoch(),
1137 group_epoch_secrets.resumption_psk().clone(),
1138 );
1139
1140 let mut bindings: crate::components::vc_derivation_info::VcEmulationBindings = provider
1146 .storage()
1147 .vc_emulation_bindings(public_group.group_id())
1148 .map_err(Error::StorageError)?
1149 .unwrap_or_default();
1150 let max_entries = message_secrets_store.max_epochs.saturating_add(1);
1151 bindings.insert(public_group.group_context().epoch(), epoch_id, max_entries);
1152 provider
1153 .storage()
1154 .write_vc_emulation_bindings(public_group.group_id(), &bindings)
1155 .map_err(Error::StorageError)?;
1156
1157 let mls_group = MlsGroup {
1158 mls_group_config: join_config.clone(),
1159 own_leaf_nodes: vec![],
1160 aad: vec![],
1161 #[cfg(feature = "extensions-draft")]
1162 safe_aad: crate::framing::SafeAad::empty(),
1163 group_state: MlsGroupState::Operational,
1164 public_group,
1165 group_epoch_secrets,
1166 own_leaf_index: creator_index,
1167 message_secrets_store,
1168 resumption_psk_store,
1169 #[cfg(feature = "extensions-draft")]
1170 application_export_tree: None,
1171 #[cfg(feature = "virtual-clients-draft")]
1172 emulation_group: false,
1173 };
1174 mls_group
1175 .store(provider.storage())
1176 .map_err(Error::StorageError)?;
1177 mls_group
1178 .store_epoch_keypairs(provider.storage(), &[leaf_keypair])
1179 .map_err(Error::StorageError)?;
1180
1181 Ok(mls_group)
1182 }
1183}
1184
1185#[cfg(feature = "virtual-clients-draft")]
1199#[derive(Debug, Default)]
1200pub struct VcExternalCommitJoinBuilder {
1201 join_config: MlsGroupJoinConfig,
1202 ratchet_tree: Option<RatchetTreeIn>,
1203 lifetime_policy: LeafNodeLifetimePolicy,
1204}
1205
1206#[cfg(feature = "virtual-clients-draft")]
1207impl VcExternalCommitJoinBuilder {
1208 pub fn new() -> Self {
1210 Self::default()
1211 }
1212
1213 pub fn with_config(mut self, join_config: MlsGroupJoinConfig) -> Self {
1215 self.join_config = join_config;
1216 self
1217 }
1218
1219 pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
1224 self.ratchet_tree = Some(ratchet_tree);
1225 self
1226 }
1227
1228 pub fn skip_lifetime_validation(mut self) -> Self {
1233 self.lifetime_policy = LeafNodeLifetimePolicy::Skip;
1234 self
1235 }
1236
1237 pub fn process_commit<Provider: OpenMlsProvider>(
1249 self,
1250 provider: &Provider,
1251 verifiable_group_info: VerifiableGroupInfo,
1252 external_commit: impl Into<crate::framing::ProtocolMessage>,
1253 epoch_id: crate::components::vc_derivation_info::EpochId,
1254 ) -> Result<
1255 StagedVcExternalCommitJoin,
1256 crate::group::errors::VcExternalCommitJoinError<Provider::StorageError>,
1257 > {
1258 use tls_codec::DeserializeBytes as _;
1259
1260 use crate::{
1261 components::vc_derivation_info::{
1262 DerivationInfo, VirtualClientsError, VC_COMPONENT_ID,
1263 },
1264 framing::Sender,
1265 group::config::PastEpochDeletionPolicy,
1266 group::errors::{ProcessMessageError, VcExternalCommitJoinError as Error},
1267 group::mls_group::processing::committed_app_data_update_proposals,
1268 group::public_group::PublicGroup,
1269 prelude::mls_content::FramedContentBody,
1270 schedule::{EpochSecrets, InitSecret},
1271 };
1272
1273 let Self {
1274 join_config,
1275 ratchet_tree,
1276 lifetime_policy,
1277 } = self;
1278
1279 let ratchet_tree = match verifiable_group_info.extensions().ratchet_tree() {
1282 Some(extension) => extension.ratchet_tree().clone(),
1283 None => ratchet_tree.ok_or(Error::MissingRatchetTree)?,
1284 };
1285 let (public_group, _group_info) = PublicGroup::from_ratchet_tree(
1286 provider.crypto(),
1287 ratchet_tree,
1288 verifiable_group_info,
1289 ProposalStore::new(),
1290 lifetime_policy,
1291 )?;
1292
1293 let ciphersuite = public_group.ciphersuite();
1300 let serialized_group_context = public_group
1301 .group_context()
1302 .tls_serialize_detached()
1303 .map_err(LibraryError::missing_bound_check)?;
1304 let own_leaf_index = public_group.leftmost_free_index(std::iter::empty())?;
1305 let init_secret = InitSecret::random(ciphersuite, provider.rand())
1306 .map_err(LibraryError::unexpected_crypto_error)?;
1307 let epoch_secrets =
1308 EpochSecrets::with_init_secret(provider.crypto(), ciphersuite, init_secret)
1309 .map_err(LibraryError::unexpected_crypto_error)?;
1310 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
1311 serialized_group_context,
1312 public_group.tree_size(),
1313 LeafNodeIndex::new(0u32),
1314 );
1315 let message_secrets_store = MessageSecretsStore::new_with_secret(
1318 &PastEpochDeletionPolicy::MaxEpochs(0),
1319 message_secrets,
1320 );
1321 let resumption_psk_store = ResumptionPskStore::new(join_config.number_of_resumption_psks);
1322 let mut group = MlsGroup {
1323 mls_group_config: join_config,
1324 own_leaf_nodes: vec![],
1325 aad: vec![],
1326 #[cfg(feature = "extensions-draft")]
1327 safe_aad: crate::framing::SafeAad::empty(),
1328 group_state: MlsGroupState::Operational,
1329 public_group,
1330 group_epoch_secrets,
1331 own_leaf_index,
1332 message_secrets_store,
1333 resumption_psk_store,
1334 #[cfg(feature = "extensions-draft")]
1335 application_export_tree: None,
1336 emulation_group: false,
1339 };
1340
1341 let processing::UnprotectedMessage::Unverified(unverified) =
1344 group.unprotect_message(provider, external_commit)?
1345 else {
1346 return Err(Error::NotAnExternalCommit);
1347 };
1348 let verified = unverified
1349 .verify(group.ciphersuite(), provider.crypto(), group.version())
1350 .map_err(ProcessMessageError::from)?;
1351 if !matches!(verified.content.sender(), Sender::NewMemberCommit) {
1352 return Err(Error::NotAnExternalCommit);
1353 }
1354 let content = verified.content;
1355 let FramedContentBody::Commit(commit) = content.content() else {
1356 return Err(Error::NotAnExternalCommit);
1357 };
1358
1359 let derivation_info_bytes = commit
1364 .path
1365 .as_ref()
1366 .and_then(|path| path.leaf_node().extensions().app_data_dictionary())
1367 .and_then(|dict| dict.dictionary().get(&VC_COMPONENT_ID))
1368 .ok_or(Error::MissingDerivationInfo)?;
1369 let derivation_info = DerivationInfo::tls_deserialize_exact_bytes(derivation_info_bytes)
1370 .map_err(|_| VirtualClientsError::DerivationInfoMalformed)?;
1371 if derivation_info.epoch_id() != &epoch_id {
1372 return Err(Error::EpochIdMismatch);
1373 }
1374
1375 let app_data_update_proposals =
1381 committed_app_data_update_proposals(commit, group.proposal_store());
1382
1383 Ok(StagedVcExternalCommitJoin {
1384 group,
1385 content,
1386 app_data_update_proposals,
1387 app_data_updates: None,
1388 })
1389 }
1390}
1391
1392#[cfg(feature = "virtual-clients-draft")]
1408pub struct StagedVcExternalCommitJoin {
1409 group: MlsGroup,
1411 content: AuthenticatedContent,
1413 app_data_update_proposals: Vec<AppDataUpdateProposal>,
1416 app_data_updates: Option<AppDataUpdates>,
1419}
1420
1421#[cfg(feature = "virtual-clients-draft")]
1422impl StagedVcExternalCommitJoin {
1423 pub fn prior_group_context(&self) -> &GroupContext {
1428 self.group.context()
1429 }
1430
1431 pub fn prior_members(&self) -> impl Iterator<Item = Member> + '_ {
1436 self.group.members()
1437 }
1438
1439 pub fn app_data_update_proposals(&self) -> impl Iterator<Item = &AppDataUpdateProposal> {
1443 self.app_data_update_proposals.iter()
1444 }
1445
1446 pub fn app_ephemeral_proposals_for_component_id(
1455 &self,
1456 component_id: ComponentId,
1457 ) -> impl Iterator<Item = &AppEphemeralProposal> {
1458 use crate::{
1459 messages::proposals::{Proposal, ProposalOrRef},
1460 prelude::mls_content::FramedContentBody,
1461 };
1462
1463 let proposals = match self.content.content() {
1464 FramedContentBody::Commit(commit) => commit.proposals.as_slice(),
1465 _ => &[],
1467 };
1468 proposals
1469 .iter()
1470 .filter_map(move |proposal_or_ref| match proposal_or_ref {
1471 ProposalOrRef::Proposal(proposal) => match proposal.as_ref() {
1472 Proposal::AppEphemeral(app_ephemeral)
1473 if app_ephemeral.component_id() == component_id =>
1474 {
1475 Some(app_ephemeral.as_ref())
1476 }
1477 _ => None,
1478 },
1479 ProposalOrRef::Reference(_) => None,
1480 })
1481 }
1482
1483 pub fn app_data_dictionary_updater(&self) -> AppDataDictionaryUpdater<'_> {
1486 AppDataDictionaryUpdater::new(self.group.context().app_data_dict())
1487 }
1488
1489 pub fn with_app_data_dictionary_updates(&mut self, app_data_updates: Option<AppDataUpdates>) {
1493 self.app_data_updates = app_data_updates;
1494 }
1495
1496 pub fn into_group<Provider: OpenMlsProvider>(
1505 self,
1506 provider: &Provider,
1507 ) -> Result<MlsGroup, crate::group::errors::VcExternalCommitJoinError<Provider::StorageError>>
1508 {
1509 use crate::{
1510 group::errors::{StageCommitError, VcExternalCommitJoinError as Error},
1511 group::public_group::errors::ApplyAppDataUpdateError,
1512 prelude::mls_content::FramedContentBody,
1513 };
1514
1515 let Self {
1516 mut group,
1517 content,
1518 app_data_update_proposals,
1519 app_data_updates,
1520 } = self;
1521
1522 if !app_data_update_proposals.is_empty() && app_data_updates.is_none() {
1528 return Err(StageCommitError::ApplyAppDataUpdateError(
1529 ApplyAppDataUpdateError::MissingAppDataUpdates,
1530 )
1531 .into());
1532 }
1533
1534 let FramedContentBody::Commit(commit) = content.content() else {
1535 return Err(LibraryError::custom("staged join without commit content").into());
1537 };
1538
1539 let material = group
1543 .load_vc_commit_material(provider, commit)?
1544 .ok_or(Error::MissingDerivationInfo)?;
1545 let staged = group.stage_commit_with_app_data_updates(
1546 &content,
1547 vec![],
1548 vec![],
1549 app_data_updates,
1550 provider,
1551 Some(material),
1552 )?;
1553 group.merge_staged_commit(provider, staged)?;
1554 let deletion_policy = group.mls_group_config.past_epoch_deletion_policy().clone();
1555 group.resize_message_secrets_store(&deletion_policy);
1556 group
1557 .store(provider.storage())
1558 .map_err(Error::StorageError)?;
1559 Ok(group)
1560 }
1561}
1562
1563#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1566pub enum LeafNodeLifetimePolicy {
1567 #[default]
1571 Verify,
1572
1573 Skip,
1575}
1576
1577pub struct JoinBuilder<'a, Provider: OpenMlsProvider> {
1581 provider: &'a Provider,
1582 processed_welcome: ProcessedWelcome,
1583 ratchet_tree: Option<RatchetTreeIn>,
1584 validate_lifetimes: LeafNodeLifetimePolicy,
1585 replace_old_group: bool,
1586}
1587
1588impl<'a, Provider: OpenMlsProvider> JoinBuilder<'a, Provider> {
1589 pub fn new(provider: &'a Provider, processed_welcome: ProcessedWelcome) -> Self {
1591 Self {
1592 provider,
1593 processed_welcome,
1594 ratchet_tree: None,
1595 replace_old_group: false,
1596 validate_lifetimes: LeafNodeLifetimePolicy::Verify,
1597 }
1598 }
1599
1600 pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
1602 self.ratchet_tree = Some(ratchet_tree);
1603 self
1604 }
1605
1606 pub fn replace_old_group(mut self) -> Self {
1608 self.replace_old_group = true;
1609 self
1610 }
1611
1612 pub fn skip_lifetime_validation(mut self) -> Self {
1617 self.validate_lifetimes = LeafNodeLifetimePolicy::Skip;
1618 self
1619 }
1620
1621 pub fn processed_welcome(&self) -> &ProcessedWelcome {
1625 &self.processed_welcome
1626 }
1627
1628 pub fn build(self) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
1630 self.processed_welcome.into_staged_welcome_inner(
1631 self.provider,
1632 self.ratchet_tree,
1633 self.validate_lifetimes,
1634 self.replace_old_group,
1635 )
1636 }
1637}