1use past_secrets::MessageSecretsStore;
7use proposal_store::ProposalQueue;
8use serde::{Deserialize, Serialize};
9use tls_codec::Serialize as _;
10
11#[cfg(test)]
12use crate::treesync::node::leaf_node::TreePosition;
13
14use super::proposal_store::{ProposalStore, QueuedProposal};
15use crate::{
16 binary_tree::array_representation::LeafNodeIndex,
17 ciphersuite::{hash_ref::ProposalRef, signable::Signable},
18 credentials::Credential,
19 error::LibraryError,
20 extensions::Extensions,
21 framing::{mls_auth_content::AuthenticatedContent, *},
22 group::{
23 CreateGroupContextExtProposalError, DeletePastEpochSecretsError, Extension, ExtensionType,
24 ExternalPubExtension, GroupContext, GroupEpoch, GroupId, MlsGroupJoinConfig,
25 MlsGroupStateError, OutgoingWireFormatPolicy, PublicGroup, RatchetTreeExtension,
26 RequiredCapabilitiesExtension, SetPastEpochDeletionPolicyError, StagedCommit,
27 },
28 key_packages::KeyPackageBundle,
29 messages::{
30 group_info::{GroupInfo, GroupInfoTBS, VerifiableGroupInfo},
31 proposals::*,
32 ConfirmationTag, GroupSecrets, Welcome,
33 },
34 schedule::{
35 message_secrets::MessageSecrets,
36 psk::{load_psks, store::ResumptionPskStore, PskSecret},
37 GroupEpochSecrets, JoinerSecret, KeySchedule,
38 },
39 storage::{OpenMlsProvider, StorageProvider},
40 treesync::{
41 node::{encryption_keys::EncryptionKeyPair, leaf_node::LeafNode},
42 RatchetTree, TreeSync,
43 },
44 versions::ProtocolVersion,
45};
46use openmls_traits::{
47 crypto::OpenMlsCrypto, signatures::Signer, storage::StorageProvider as _, types::Ciphersuite,
48};
49
50#[cfg(feature = "extensions-draft")]
51use crate::schedule::{application_export_tree::ApplicationExportTree, ApplicationExportSecret};
52
53mod application;
55mod exporting;
56mod updates;
57
58#[cfg(feature = "virtual-clients-draft")]
59pub use application::UnconfirmedMessage;
60
61use config::*;
62
63pub(crate) mod builder;
65pub(crate) mod commit_builder;
66pub(crate) mod config;
67pub(crate) mod creation;
68pub(crate) mod errors;
69pub(crate) mod membership;
70pub(crate) mod past_secrets;
71pub(crate) mod processing;
72pub(crate) mod proposal;
73pub(crate) mod proposal_store;
74pub(crate) mod staged_commit;
75
76#[cfg(feature = "extensions-draft")]
77pub(crate) mod app_ephemeral;
78
79#[cfg(test)]
81pub(crate) mod tests_and_kats;
82
83#[derive(Debug)]
84pub(crate) struct CreateCommitResult {
85 pub(crate) commit: AuthenticatedContent,
86 pub(crate) welcome_option: Option<Welcome>,
87 pub(crate) staged_commit: StagedCommit,
88 pub(crate) group_info: Option<GroupInfo>,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub struct Member {
94 pub index: LeafNodeIndex,
96 pub credential: Credential,
98 pub encryption_key: Vec<u8>,
100 pub signature_key: Vec<u8>,
102}
103
104impl Member {
105 pub fn new(
107 index: LeafNodeIndex,
108 encryption_key: Vec<u8>,
109 signature_key: Vec<u8>,
110 credential: Credential,
111 ) -> Self {
112 Self {
113 index,
114 encryption_key,
115 signature_key,
116 credential,
117 }
118 }
119}
120
121#[derive(Debug, Serialize, Deserialize)]
124#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
125pub enum PendingCommitState {
126 Member(StagedCommit),
128 External(StagedCommit),
130}
131
132impl PendingCommitState {
133 pub(crate) fn staged_commit(&self) -> &StagedCommit {
136 match self {
137 PendingCommitState::Member(pc) => pc,
138 PendingCommitState::External(pc) => pc,
139 }
140 }
141}
142
143impl From<PendingCommitState> for StagedCommit {
144 fn from(pcs: PendingCommitState) -> Self {
145 match pcs {
146 PendingCommitState::Member(pc) => pc,
147 PendingCommitState::External(pc) => pc,
148 }
149 }
150}
151
152#[derive(Debug, Serialize, Deserialize)]
197#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
198pub enum MlsGroupState {
199 PendingCommit(Box<PendingCommitState>),
201 Operational,
203 Inactive,
205}
206
207#[derive(Debug)]
232#[cfg_attr(feature = "test-utils", derive(Clone, PartialEq))]
233pub struct MlsGroup {
234 mls_group_config: MlsGroupJoinConfig,
236 public_group: PublicGroup,
238 group_epoch_secrets: GroupEpochSecrets,
240 own_leaf_index: LeafNodeIndex,
242 message_secrets_store: MessageSecretsStore,
249 resumption_psk_store: ResumptionPskStore,
251 own_leaf_nodes: Vec<LeafNode>,
255 aad: Vec<u8>,
259 #[cfg(feature = "extensions-draft")]
263 safe_aad: SafeAad,
264 group_state: MlsGroupState,
267 #[cfg(feature = "extensions-draft")]
271 application_export_tree: Option<ApplicationExportTree>,
272}
273
274impl MlsGroup {
275 pub fn configuration(&self) -> &MlsGroupJoinConfig {
279 &self.mls_group_config
280 }
281
282 pub fn set_configuration<Storage: StorageProvider>(
284 &mut self,
285 storage: &Storage,
286 mls_group_config: &MlsGroupJoinConfig,
287 ) -> Result<(), Storage::Error> {
288 self.mls_group_config = mls_group_config.clone();
289 storage.write_mls_join_config(self.group_id(), mls_group_config)
290 }
291
292 pub fn set_aad(&mut self, aad: Vec<u8>) {
296 self.aad = aad;
297 }
298
299 pub fn aad(&self) -> &[u8] {
302 &self.aad
303 }
304
305 #[cfg(feature = "extensions-draft")]
315 pub fn set_safe_aad(&mut self, items: Vec<SafeAadItem>) -> Result<(), SafeAadError> {
316 self.safe_aad = SafeAad::from_items(items)?;
317 Ok(())
318 }
319
320 #[cfg(feature = "extensions-draft")]
323 pub fn safe_aad_items(&self) -> &[SafeAadItem] {
324 self.safe_aad.items()
325 }
326
327 pub fn ciphersuite(&self) -> Ciphersuite {
331 self.public_group.ciphersuite()
332 }
333
334 pub fn confirmation_tag(&self) -> &ConfirmationTag {
336 self.public_group.confirmation_tag()
337 }
338
339 pub fn is_active(&self) -> bool {
342 !matches!(self.group_state, MlsGroupState::Inactive)
343 }
344
345 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
348 if !self.is_active() {
349 return Err(MlsGroupStateError::UseAfterEviction);
350 }
351 self.public_group
352 .leaf(self.own_leaf_index())
353 .map(|node| node.credential())
354 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
355 }
356
357 pub fn own_leaf_index(&self) -> LeafNodeIndex {
359 self.own_leaf_index
360 }
361
362 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
364 self.public_group().leaf(self.own_leaf_index())
365 }
366
367 pub fn group_id(&self) -> &GroupId {
369 self.public_group.group_id()
370 }
371
372 pub fn epoch(&self) -> GroupEpoch {
374 self.public_group.group_context().epoch()
375 }
376
377 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
379 self.proposal_store().proposals()
380 }
381
382 pub fn treesync(&self) -> &TreeSync {
384 self.public_group.treesync()
385 }
386
387 pub fn pending_commit(&self) -> Option<&StagedCommit> {
391 match self.group_state {
392 MlsGroupState::PendingCommit(ref pending_commit_state) => {
393 Some(pending_commit_state.staged_commit())
394 }
395 MlsGroupState::Operational => None,
396 MlsGroupState::Inactive => None,
397 }
398 }
399
400 pub fn clear_pending_commit<Storage: StorageProvider>(
412 &mut self,
413 storage: &Storage,
414 ) -> Result<(), Storage::Error> {
415 match self.group_state {
416 MlsGroupState::PendingCommit(ref pending_commit_state) => {
417 if let PendingCommitState::Member(_) = **pending_commit_state {
418 self.group_state = MlsGroupState::Operational;
419 storage.write_group_state(self.group_id(), &self.group_state)
420 } else {
421 Ok(())
422 }
423 }
424 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
425 }
426 }
427
428 pub fn clear_pending_proposals<Storage: StorageProvider>(
435 &mut self,
436 storage: &Storage,
437 ) -> Result<(), Storage::Error> {
438 if !self.proposal_store().is_empty() {
440 self.proposal_store_mut().empty();
442
443 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
445 }
446
447 Ok(())
448 }
449
450 pub fn extensions(&self) -> &Extensions<GroupContext> {
452 self.public_group().group_context().extensions()
453 }
454
455 pub fn ext_commit_sender_index(
457 &self,
458 commit: &StagedCommit,
459 ) -> Result<LeafNodeIndex, LibraryError> {
460 self.public_group().ext_commit_sender_index(commit)
461 }
462
463 pub fn load<Storage: crate::storage::StorageProvider>(
467 storage: &Storage,
468 group_id: &GroupId,
469 ) -> Result<Option<MlsGroup>, Storage::Error> {
470 let public_group = PublicGroup::load(storage, group_id)?;
471 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
472 let own_leaf_index = storage.own_leaf_index(group_id)?;
473 let message_secrets_store = storage.message_secrets(group_id)?;
474 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
475 let mls_group_config = storage.mls_group_join_config(group_id)?;
476 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
477 let group_state = storage.group_state(group_id)?;
478 #[cfg(feature = "extensions-draft")]
479 let application_export_tree = storage.application_export_tree(group_id)?;
480
481 let build = || -> Option<Self> {
482 Some(Self {
483 public_group: public_group?,
484 group_epoch_secrets: group_epoch_secrets?,
485 own_leaf_index: own_leaf_index?,
486 message_secrets_store: message_secrets_store?,
487 resumption_psk_store: resumption_psk_store?,
488 mls_group_config: mls_group_config?,
489 own_leaf_nodes,
490 aad: vec![],
491 #[cfg(feature = "extensions-draft")]
492 safe_aad: SafeAad::empty(),
493 group_state: group_state?,
494 #[cfg(feature = "extensions-draft")]
495 application_export_tree,
496 })
497 };
498
499 Ok(build())
500 }
501
502 pub fn delete<Storage: crate::storage::StorageProvider>(
506 &mut self,
507 storage: &Storage,
508 ) -> Result<(), Storage::Error> {
509 PublicGroup::delete(storage, self.group_id())?;
510 storage.delete_own_leaf_index(self.group_id())?;
511 storage.delete_group_epoch_secrets(self.group_id())?;
512 storage.delete_message_secrets(self.group_id())?;
513 storage.delete_all_resumption_psk_secrets(self.group_id())?;
514 storage.delete_group_config(self.group_id())?;
515 storage.delete_own_leaf_nodes(self.group_id())?;
516 storage.delete_group_state(self.group_id())?;
517 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
518
519 #[cfg(feature = "extensions-draft")]
520 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
521
522 #[cfg(feature = "virtual-clients-draft")]
527 storage.delete_vc_emulation_bindings(self.group_id())?;
528
529 self.proposal_store_mut().empty();
530 storage.delete_encryption_epoch_key_pairs(
531 self.group_id(),
532 &self.epoch(),
533 self.own_leaf_index().u32(),
534 )?;
535
536 Ok(())
537 }
538
539 pub fn export_ratchet_tree(&self) -> RatchetTree {
543 self.public_group().export_ratchet_tree()
544 }
545}
546
547#[cfg(feature = "virtual-clients-draft")]
553#[derive(thiserror::Error, Debug, PartialEq, Clone)]
554pub(crate) enum VcEmulationStateError<StorageError> {
555 #[error("Error reading the binding or emulation-epoch state from storage: {0}")]
557 Storage(StorageError),
558 #[error("The group is bound to an emulation epoch, but its state is missing.")]
560 MissingEmulationEpochState,
561}
562
563impl MlsGroup {
565 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
567 self.public_group.required_capabilities()
568 }
569
570 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
572 &self.group_epoch_secrets
573 }
574
575 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
577 self.message_secrets_store.message_secrets()
578 }
579
580 pub(crate) fn resize_message_secrets_store(&mut self, policy: &PastEpochDeletionPolicy) {
584 self.message_secrets_store.resize(policy);
585 }
586
587 pub fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
589 self.mls_group_config.past_epoch_deletion_policy()
590 }
591
592 pub fn set_past_epoch_deletion_policy<Provider: OpenMlsProvider>(
594 &mut self,
595 provider: &Provider,
596 policy: PastEpochDeletionPolicy,
597 ) -> Result<(), SetPastEpochDeletionPolicyError<Provider::StorageError>> {
598 self.resize_message_secrets_store(&policy);
600
601 self.mls_group_config.past_epoch_deletion_policy = policy;
603
604 provider
606 .storage()
607 .write_mls_join_config(self.group_id(), &self.mls_group_config)?;
608
609 provider
611 .storage()
612 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
613
614 Ok(())
615 }
616
617 pub(crate) fn message_secrets_for_epoch_mut(
619 &mut self,
620 epoch: GroupEpoch,
621 ) -> Result<&mut MessageSecrets, SecretTreeError> {
622 if epoch < self.context().epoch() {
623 self.message_secrets_store
624 .secrets_for_epoch_mut(epoch)
625 .ok_or(SecretTreeError::TooDistantInThePast)
626 } else {
627 Ok(self.message_secrets_store.message_secrets_mut())
628 }
629 }
630
631 pub(crate) fn message_secrets_for_epoch(
633 &self,
634 epoch: GroupEpoch,
635 ) -> Result<&MessageSecrets, SecretTreeError> {
636 if epoch < self.context().epoch() {
637 self.message_secrets_store
638 .secrets_for_epoch(epoch)
639 .ok_or(SecretTreeError::TooDistantInThePast)
640 } else {
641 Ok(self.message_secrets_store.message_secrets())
642 }
643 }
644
645 pub(crate) fn message_secrets_and_leaves(
651 &self,
652 epoch: GroupEpoch,
653 ) -> Result<(&MessageSecrets, &[Member]), SecretTreeError> {
654 if epoch < self.context().epoch() {
655 self.message_secrets_store
656 .secrets_and_leaves_for_epoch(epoch)
657 .ok_or(SecretTreeError::TooDistantInThePast)
658 } else {
659 Ok((self.message_secrets_store.message_secrets(), &[]))
662 }
663 }
664
665 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
667 &self,
668 framing_parameters: FramingParameters,
669 extensions: Extensions<GroupContext>,
670 signer: &impl Signer,
671 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
672 {
673 let required_extension = extensions
675 .iter()
676 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
677 if let Some(required_extension) = required_extension {
678 let required_capabilities = required_extension.as_required_capabilities_extension()?;
679 self.own_leaf_node()
681 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
682 .capabilities()
683 .supports_required_capabilities(required_capabilities)?;
684
685 self.public_group()
688 .check_extension_support(required_capabilities.extension_types())?;
689 }
690 let proposal = GroupContextExtensionProposal::new(extensions);
691 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
692 AuthenticatedContent::member_proposal(
693 framing_parameters,
694 self.own_leaf_index(),
695 proposal,
696 self.context(),
697 signer,
698 )
699 .map_err(|e| e.into())
700 }
701
702 #[cfg(feature = "virtual-clients-draft")]
710 pub(crate) fn vc_emulation_state_at_epoch<Storage: StorageProvider>(
711 &self,
712 storage: &Storage,
713 epoch: GroupEpoch,
714 ) -> Result<
715 Option<crate::components::vc_derivation_info::EmulationEpochState>,
716 VcEmulationStateError<Storage::Error>,
717 > {
718 let bindings: Option<crate::components::vc_derivation_info::VcEmulationBindings> = storage
719 .vc_emulation_bindings(self.group_id())
720 .map_err(VcEmulationStateError::Storage)?;
721 let Some(epoch_id) = bindings.and_then(|bindings| bindings.get(epoch).cloned()) else {
722 return Ok(None);
723 };
724 let state = storage
725 .vc_emulation_epoch_state(&epoch_id)
726 .map_err(VcEmulationStateError::Storage)?
727 .ok_or_else(|| {
728 log::error!("vc: group is bound to emulation epoch, but state is missing");
729 VcEmulationStateError::MissingEmulationEpochState
730 })?;
731 Ok(Some(state))
732 }
733
734 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
736 &mut self,
737 public_message: AuthenticatedContent,
738 provider: &Provider,
739 ) -> Result<EncryptionOutput, MessageEncryptionError<Provider::StorageError>> {
740 let padding_size = self.configuration().padding_size();
741
742 #[cfg(feature = "virtual-clients-draft")]
746 let emulation_state = self
747 .vc_emulation_state_at_epoch(provider.storage(), self.epoch())
748 .map_err(|e| match e {
749 VcEmulationStateError::Storage(e) => MessageEncryptionError::StorageError(e),
750 VcEmulationStateError::MissingEmulationEpochState => {
751 MessageEncryptionError::VirtualClientsError(
752 crate::components::vc_derivation_info::VirtualClientsError::MissingEmulationEpochState,
753 )
754 }
755 })?;
756 #[cfg(feature = "virtual-clients-draft")]
757 let emulator_ctx: Option<crate::framing::EmulatorReuseGuardCtx<'_>> = emulation_state
758 .as_ref()
759 .map(|state| state.reuse_guard_inputs());
760
761 let msg = PrivateMessage::try_from_authenticated_content(
762 provider.crypto(),
763 provider.rand(),
764 &public_message,
765 self.ciphersuite(),
766 self.message_secrets_store.message_secrets_mut(),
767 padding_size,
768 #[cfg(feature = "virtual-clients-draft")]
769 emulator_ctx.as_ref(),
770 )?;
771
772 #[cfg(feature = "virtual-clients-draft")]
777 let msg = {
778 let mut msg = msg;
779 if let Some(state) = &emulation_state {
780 if public_message.content().content_type() == ContentType::Application {
781 let generation_id = state
782 .derive_generation_id(
783 provider.crypto(),
784 self.group_id(),
785 self.epoch(),
786 msg.generation,
787 crate::components::vc_derivation_info::RatchetType::Application,
788 )
789 .map_err(MessageEncryptionError::VirtualClientsError)?;
790 msg.generation_id = Some(generation_id);
791 }
792 }
793 msg
794 };
795
796 provider
797 .storage()
798 .write_message_secrets(self.group_id(), &self.message_secrets_store)
799 .map_err(MessageEncryptionError::StorageError)?;
800
801 Ok(msg)
802 }
803
804 pub(crate) fn outgoing_wire_format(&self) -> WireFormat {
806 self.mls_group_config.wire_format_policy().outgoing().into()
807 }
808
809 pub(crate) fn outgoing_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
815 #[cfg(feature = "extensions-draft")]
816 {
817 self.assembled_authenticated_data()
818 }
819 #[cfg(not(feature = "extensions-draft"))]
820 {
821 Ok(self.aad.clone())
822 }
823 }
824
825 #[cfg(feature = "extensions-draft")]
830 pub(crate) fn assembled_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
831 if !self.context().safe_aad_required() {
832 return Ok(self.aad.clone());
833 }
834 crate::framing::safe_aad::assemble_authenticated_data(&self.safe_aad, &self.aad)
835 .map_err(|_| LibraryError::custom("SafeAad serialization failed"))
836 }
837
838 pub fn delete_past_epoch_secrets<Provider: OpenMlsProvider>(
842 &mut self,
843 provider: &Provider,
844 policy: PastEpochDeletion,
845 ) -> Result<(), DeletePastEpochSecretsError<Provider::StorageError>> {
846 self.message_secrets_store.delete_past_epoch_secrets(policy);
848 provider
850 .storage()
851 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
852
853 Ok(())
854 }
855
856 pub fn proposal_store(&self) -> &ProposalStore {
858 self.public_group.proposal_store()
859 }
860
861 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
863 self.public_group.proposal_store_mut()
864 }
865
866 pub(crate) fn context(&self) -> &GroupContext {
868 self.public_group.group_context()
869 }
870
871 pub(crate) fn version(&self) -> ProtocolVersion {
873 self.public_group.version()
874 }
875
876 #[inline]
878 pub(crate) fn reset_aad(&mut self) {
879 self.aad.clear();
880 #[cfg(feature = "extensions-draft")]
881 {
882 self.safe_aad = SafeAad::empty();
883 }
884 }
885
886 pub fn public_group(&self) -> &PublicGroup {
888 &self.public_group
889 }
890}
891
892impl MlsGroup {
894 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
899 &self,
900 store: &Storage,
901 keypair_references: &[EncryptionKeyPair],
902 ) -> Result<(), Storage::Error> {
903 store.write_encryption_epoch_key_pairs(
904 self.group_id(),
905 &self.context().epoch(),
906 self.own_leaf_index().u32(),
907 keypair_references,
908 )
909 }
910
911 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
916 &self,
917 store: &Storage,
918 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
919 store.encryption_epoch_key_pairs(
920 self.group_id(),
921 &self.context().epoch(),
922 self.own_leaf_index().u32(),
923 )
924 }
925
926 #[cfg(not(feature = "virtual-clients-draft"))]
931 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
932 &self,
933 store: &Storage,
934 ) -> Result<(), Storage::Error> {
935 store.delete_encryption_epoch_key_pairs(
936 self.group_id(),
937 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
938 self.own_leaf_index().u32(),
939 )
940 }
941
942 #[cfg(feature = "virtual-clients-draft")]
943 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
944 &self,
945 store: &Storage,
946 previous_own_leaf_index: LeafNodeIndex,
947 ) -> Result<(), Storage::Error> {
948 store.delete_encryption_epoch_key_pairs(
954 self.group_id(),
955 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
956 previous_own_leaf_index.u32(),
957 )
958 }
959
960 pub(super) fn store<Storage: crate::storage::StorageProvider>(
963 &self,
964 storage: &Storage,
965 ) -> Result<(), Storage::Error> {
966 self.public_group.store(storage)?;
967 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
968 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
969 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
970 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
971 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
972 storage.write_group_state(self.group_id(), &self.group_state)?;
973 #[cfg(feature = "extensions-draft")]
974 if let Some(application_export_tree) = &self.application_export_tree {
975 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
976 }
977
978 Ok(())
979 }
980
981 fn content_to_mls_message(
985 &mut self,
986 mls_auth_content: AuthenticatedContent,
987 provider: &impl OpenMlsProvider,
988 ) -> Result<MlsMessageOut, LibraryError> {
989 let msg = match self.configuration().wire_format_policy().outgoing() {
990 OutgoingWireFormatPolicy::AlwaysPlaintext => {
991 let mut plaintext: PublicMessage = mls_auth_content.into();
992 if plaintext.sender().is_member() {
994 plaintext.set_membership_tag(
995 provider.crypto(),
996 self.ciphersuite(),
997 self.message_secrets().membership_key(),
998 self.message_secrets().serialized_context(),
999 )?;
1000 }
1001 plaintext.into()
1002 }
1003 OutgoingWireFormatPolicy::AlwaysCiphertext => {
1004 let EncryptionOutput {
1007 private_message, ..
1008 } = self
1009 .encrypt(mls_auth_content, provider)
1010 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
1012 MlsMessageOut::from_private_message(private_message, self.version())
1013 }
1014 };
1015 Ok(msg)
1016 }
1017
1018 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
1021 match self.group_state {
1022 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
1023 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
1024 MlsGroupState::Operational => Ok(()),
1025 }
1026 }
1027}
1028
1029impl MlsGroup {
1031 #[cfg(any(feature = "test-utils", test))]
1032 pub fn export_group_context(&self) -> &GroupContext {
1033 self.context()
1034 }
1035
1036 #[cfg(any(feature = "test-utils", test))]
1037 pub fn tree_hash(&self) -> &[u8] {
1038 self.public_group().group_context().tree_hash()
1039 }
1040
1041 #[cfg(any(feature = "test-utils", test))]
1042 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
1043 self.message_secrets_store.message_secrets_mut()
1044 }
1045
1046 #[cfg(any(feature = "test-utils", test))]
1047 pub fn print_ratchet_tree(&self, message: &str) {
1048 println!("{}: {}", message, self.public_group().export_ratchet_tree());
1049 }
1050
1051 #[cfg(any(feature = "test-utils", test))]
1052 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
1053 self.public_group.context_mut()
1054 }
1055
1056 #[cfg(test)]
1057 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
1058 self.own_leaf_index = own_leaf_index;
1059 }
1060
1061 #[cfg(test)]
1062 pub(crate) fn own_tree_position(&self) -> TreePosition {
1063 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
1064 }
1065
1066 #[cfg(test)]
1067 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
1068 &self.message_secrets_store
1069 }
1070
1071 #[cfg(test)]
1072 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
1073 &self.resumption_psk_store
1074 }
1075
1076 #[cfg(test)]
1077 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
1078 self.public_group.set_group_context(group_context)
1079 }
1080
1081 #[cfg(any(test, feature = "test-utils"))]
1082 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
1083 let loaded = MlsGroup::load(storage, self.group_id())
1084 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
1085 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
1086
1087 if self != &other {
1088 let mut diagnostics = Vec::new();
1089
1090 if self.mls_group_config != other.mls_group_config {
1091 diagnostics.push(format!(
1092 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
1093 self.mls_group_config, other.mls_group_config
1094 ));
1095 }
1096 if self.public_group != other.public_group {
1097 diagnostics.push(format!(
1098 "public_group:\n Current: {:?}\n Loaded: {:?}",
1099 self.public_group, other.public_group
1100 ));
1101 }
1102 if self.group_epoch_secrets != other.group_epoch_secrets {
1103 diagnostics.push(format!(
1104 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
1105 self.group_epoch_secrets, other.group_epoch_secrets
1106 ));
1107 }
1108 if self.own_leaf_index != other.own_leaf_index {
1109 diagnostics.push(format!(
1110 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
1111 self.own_leaf_index, other.own_leaf_index
1112 ));
1113 }
1114 if self.message_secrets_store != other.message_secrets_store {
1115 diagnostics.push(format!(
1116 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
1117 self.message_secrets_store, other.message_secrets_store
1118 ));
1119 }
1120 if self.resumption_psk_store != other.resumption_psk_store {
1121 diagnostics.push(format!(
1122 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
1123 self.resumption_psk_store, other.resumption_psk_store
1124 ));
1125 }
1126 if self.own_leaf_nodes != other.own_leaf_nodes {
1127 diagnostics.push(format!(
1128 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
1129 self.own_leaf_nodes, other.own_leaf_nodes
1130 ));
1131 }
1132 if self.aad != other.aad {
1133 diagnostics.push(format!(
1134 "aad:\n Current: {:?}\n Loaded: {:?}",
1135 self.aad, other.aad
1136 ));
1137 }
1138 if self.group_state != other.group_state {
1139 diagnostics.push(format!(
1140 "group_state:\n Current: {:?}\n Loaded: {:?}",
1141 self.group_state, other.group_state
1142 ));
1143 }
1144 #[cfg(feature = "extensions-draft")]
1145 if self.application_export_tree != other.application_export_tree {
1146 diagnostics.push(format!(
1147 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
1148 self.application_export_tree, other.application_export_tree
1149 ));
1150 }
1151
1152 log::error!(
1153 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
1154 diagnostics.len(),
1155 diagnostics.join("\n\n")
1156 );
1157
1158 return Err(LibraryError::custom(
1159 "Loaded group does not match current group",
1160 ));
1161 }
1162
1163 Ok(())
1164 }
1165}
1166
1167#[derive(Debug)]
1170pub struct StagedWelcome {
1171 mls_group_config: MlsGroupJoinConfig,
1173 public_group: PublicGroup,
1174 group_epoch_secrets: GroupEpochSecrets,
1175 own_leaf_index: LeafNodeIndex,
1176
1177 message_secrets_store: MessageSecretsStore,
1184
1185 #[cfg(feature = "extensions-draft")]
1188 application_export_secret: ApplicationExportSecret,
1189
1190 resumption_psk_store: ResumptionPskStore,
1192
1193 verifiable_group_info: VerifiableGroupInfo,
1195
1196 key_material: WelcomeKeyMaterial,
1198
1199 path_keypairs: Option<Vec<EncryptionKeyPair>>,
1201}
1202
1203pub struct ProcessedWelcome {
1210 mls_group_config: MlsGroupJoinConfig,
1212
1213 ciphersuite: Ciphersuite,
1216 group_secrets: GroupSecrets,
1217 key_schedule: crate::schedule::KeySchedule,
1218 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
1219 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
1220 key_material: WelcomeKeyMaterial,
1221}
1222
1223#[derive(Debug)]
1232pub(crate) enum WelcomeKeyMaterial {
1233 KeyPackage(Box<KeyPackageBundle>),
1236 #[cfg(feature = "virtual-clients-draft")]
1239 VirtualClient(crate::components::vc_derivation_info::VcWelcomeMaterial),
1240}
1241
1242impl WelcomeKeyMaterial {
1243 fn key_package_ref(
1249 &self,
1250 crypto: &impl OpenMlsCrypto,
1251 ) -> Result<crate::ciphersuite::hash_ref::KeyPackageRef, LibraryError> {
1252 match self {
1253 WelcomeKeyMaterial::KeyPackage(bundle) => bundle.key_package().hash_ref(crypto),
1254 #[cfg(feature = "virtual-clients-draft")]
1255 WelcomeKeyMaterial::VirtualClient(material) => Ok(material.key_package_ref.clone()),
1256 }
1257 }
1258
1259 fn init_private_key(&self) -> &crate::ciphersuite::HpkePrivateKey {
1261 match self {
1262 WelcomeKeyMaterial::KeyPackage(bundle) => bundle.init_private_key(),
1263 #[cfg(feature = "virtual-clients-draft")]
1264 WelcomeKeyMaterial::VirtualClient(material) => &material.init_private_key,
1265 }
1266 }
1267
1268 fn key_package_bundle(&self) -> Option<&KeyPackageBundle> {
1272 match self {
1273 WelcomeKeyMaterial::KeyPackage(bundle) => Some(bundle),
1274 #[cfg(feature = "virtual-clients-draft")]
1275 WelcomeKeyMaterial::VirtualClient(_) => None,
1276 }
1277 }
1278
1279 fn encryption_key_pair(&self) -> EncryptionKeyPair {
1281 match self {
1282 WelcomeKeyMaterial::KeyPackage(bundle) => bundle.encryption_key_pair(),
1283 #[cfg(feature = "virtual-clients-draft")]
1284 WelcomeKeyMaterial::VirtualClient(material) => material.encryption_keypair.clone(),
1285 }
1286 }
1287}