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::{InitKey, 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 = "migration-import")]
59pub(crate) mod migration_import;
60
61#[cfg(feature = "virtual-clients-draft")]
62pub use application::UnconfirmedMessage;
63pub use proposal::Propose;
64
65use config::*;
66
67pub(crate) mod builder;
69pub(crate) mod commit_builder;
70pub(crate) mod config;
71pub(crate) mod creation;
72pub(crate) mod errors;
73pub(crate) mod membership;
74pub(crate) mod past_secrets;
75pub(crate) mod processing;
76pub(crate) mod proposal;
77pub(crate) mod proposal_store;
78pub(crate) mod staged_commit;
79
80#[cfg(feature = "extensions-draft")]
81pub(crate) mod app_ephemeral;
82
83#[cfg(feature = "targeted-messages-draft")]
84mod targeted_messages;
85
86#[cfg(test)]
88pub(crate) mod tests_and_kats;
89
90#[derive(Debug)]
91pub(crate) struct CreateCommitResult {
92 pub(crate) commit: AuthenticatedContent,
93 pub(crate) welcome_option: Option<Welcome>,
94 pub(crate) staged_commit: StagedCommit,
95 pub(crate) group_info: Option<GroupInfo>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct Member {
101 pub index: LeafNodeIndex,
103 pub credential: Credential,
105 pub encryption_key: Vec<u8>,
107 pub signature_key: Vec<u8>,
109}
110
111impl Member {
112 pub fn new(
114 index: LeafNodeIndex,
115 encryption_key: Vec<u8>,
116 signature_key: Vec<u8>,
117 credential: Credential,
118 ) -> Self {
119 Self {
120 index,
121 encryption_key,
122 signature_key,
123 credential,
124 }
125 }
126}
127
128#[derive(Debug, Serialize, Deserialize)]
131#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
132pub enum PendingCommitState {
133 Member(StagedCommit),
135 External(StagedCommit),
137}
138
139impl PendingCommitState {
140 pub(crate) fn staged_commit(&self) -> &StagedCommit {
143 match self {
144 PendingCommitState::Member(pc) => pc,
145 PendingCommitState::External(pc) => pc,
146 }
147 }
148}
149
150impl From<PendingCommitState> for StagedCommit {
151 fn from(pcs: PendingCommitState) -> Self {
152 match pcs {
153 PendingCommitState::Member(pc) => pc,
154 PendingCommitState::External(pc) => pc,
155 }
156 }
157}
158
159#[derive(Debug, Serialize, Deserialize)]
204#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
205pub enum MlsGroupState {
206 PendingCommit(Box<PendingCommitState>),
208 Operational,
210 Inactive,
212}
213
214#[derive(Debug)]
239#[cfg_attr(feature = "migration-import", derive(serde::Deserialize))]
240#[cfg_attr(
241 all(feature = "migration-import", feature = "test-utils"),
242 derive(serde::Serialize)
243)]
244#[cfg_attr(feature = "test-utils", derive(Clone, PartialEq))]
245pub struct MlsGroup {
246 mls_group_config: MlsGroupJoinConfig,
248 public_group: PublicGroup,
250 group_epoch_secrets: GroupEpochSecrets,
252 own_leaf_index: LeafNodeIndex,
254 message_secrets_store: MessageSecretsStore,
261 resumption_psk_store: ResumptionPskStore,
263 own_leaf_nodes: Vec<LeafNode>,
267 aad: Vec<u8>,
271 #[cfg(feature = "extensions-draft")]
275 #[cfg_attr(
279 feature = "migration-import",
280 serde(default = "crate::framing::SafeAad::empty")
281 )]
282 safe_aad: SafeAad,
283 group_state: MlsGroupState,
286 #[cfg(feature = "extensions-draft")]
290 #[cfg_attr(feature = "migration-import", serde(default))]
294 application_export_tree: Option<ApplicationExportTree>,
295 #[cfg(feature = "virtual-clients-draft")]
299 #[cfg_attr(feature = "migration-import", serde(default))]
302 emulation_group: bool,
303}
304
305impl MlsGroup {
306 pub fn configuration(&self) -> &MlsGroupJoinConfig {
310 &self.mls_group_config
311 }
312
313 pub fn set_configuration<Storage: StorageProvider>(
315 &mut self,
316 storage: &Storage,
317 mls_group_config: &MlsGroupJoinConfig,
318 ) -> Result<(), Storage::Error> {
319 let policy_changed = self.mls_group_config.past_epoch_deletion_policy()
320 != mls_group_config.past_epoch_deletion_policy();
321
322 self.mls_group_config = mls_group_config.clone();
323 storage.write_mls_join_config(self.group_id(), mls_group_config)?;
324
325 if policy_changed {
326 self.resize_message_secrets_store(mls_group_config.past_epoch_deletion_policy());
328 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
329 }
330
331 Ok(())
332 }
333
334 pub fn set_aad(&mut self, aad: Vec<u8>) {
338 self.aad = aad;
339 }
340
341 pub fn aad(&self) -> &[u8] {
344 &self.aad
345 }
346
347 #[cfg(feature = "extensions-draft")]
357 pub fn set_safe_aad(&mut self, items: Vec<SafeAadItem>) -> Result<(), SafeAadError> {
358 self.safe_aad = SafeAad::from_items(items)?;
359 Ok(())
360 }
361
362 #[cfg(feature = "extensions-draft")]
365 pub fn safe_aad_items(&self) -> &[SafeAadItem] {
366 self.safe_aad.items()
367 }
368
369 pub fn ciphersuite(&self) -> Ciphersuite {
373 self.public_group.ciphersuite()
374 }
375
376 pub fn confirmation_tag(&self) -> &ConfirmationTag {
378 self.public_group.confirmation_tag()
379 }
380
381 pub fn is_active(&self) -> bool {
384 !matches!(self.group_state, MlsGroupState::Inactive)
385 }
386
387 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
390 if !self.is_active() {
391 return Err(MlsGroupStateError::UseAfterEviction);
392 }
393 self.public_group
394 .leaf(self.own_leaf_index())
395 .map(|node| node.credential())
396 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
397 }
398
399 pub fn own_leaf_index(&self) -> LeafNodeIndex {
401 self.own_leaf_index
402 }
403
404 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
406 self.public_group().leaf(self.own_leaf_index())
407 }
408
409 pub fn group_id(&self) -> &GroupId {
411 self.public_group.group_id()
412 }
413
414 pub fn epoch(&self) -> GroupEpoch {
416 self.public_group.group_context().epoch()
417 }
418
419 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
421 self.proposal_store().proposals()
422 }
423
424 pub fn treesync(&self) -> &TreeSync {
426 self.public_group.treesync()
427 }
428
429 pub fn pending_commit(&self) -> Option<&StagedCommit> {
433 match self.group_state {
434 MlsGroupState::PendingCommit(ref pending_commit_state) => {
435 Some(pending_commit_state.staged_commit())
436 }
437 MlsGroupState::Operational => None,
438 MlsGroupState::Inactive => None,
439 }
440 }
441
442 pub fn clear_pending_commit<Storage: StorageProvider>(
454 &mut self,
455 storage: &Storage,
456 ) -> Result<(), Storage::Error> {
457 match self.group_state {
458 MlsGroupState::PendingCommit(ref pending_commit_state) => {
459 if let PendingCommitState::Member(_) = **pending_commit_state {
460 self.group_state = MlsGroupState::Operational;
461 storage.write_group_state(self.group_id(), &self.group_state)
462 } else {
463 Ok(())
464 }
465 }
466 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
467 }
468 }
469
470 pub fn clear_pending_proposals<Storage: StorageProvider>(
477 &mut self,
478 storage: &Storage,
479 ) -> Result<(), Storage::Error> {
480 if !self.proposal_store().is_empty() {
482 self.proposal_store_mut().empty();
484
485 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
487 }
488
489 Ok(())
490 }
491
492 pub fn extensions(&self) -> &Extensions<GroupContext> {
494 self.public_group().group_context().extensions()
495 }
496
497 pub fn ext_commit_sender_index(
499 &self,
500 commit: &StagedCommit,
501 ) -> Result<LeafNodeIndex, LibraryError> {
502 self.public_group().ext_commit_sender_index(commit)
503 }
504
505 pub fn load<Storage: crate::storage::StorageProvider>(
509 storage: &Storage,
510 group_id: &GroupId,
511 ) -> Result<Option<MlsGroup>, Storage::Error> {
512 let public_group = PublicGroup::load(storage, group_id)?;
513 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
514 let own_leaf_index = storage.own_leaf_index(group_id)?;
515 let message_secrets_store = storage.message_secrets(group_id)?;
516 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
517 let mls_group_config = storage.mls_group_join_config(group_id)?;
518 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
519 let group_state = storage.group_state(group_id)?;
520 #[cfg(feature = "extensions-draft")]
521 let application_export_tree = storage.application_export_tree(group_id)?;
522 #[cfg(feature = "virtual-clients-draft")]
526 let emulation_group =
527 crate::components::vc_derivation_info::newest_vc_derivation_epoch(storage, group_id)?
528 .is_some();
529
530 let build = || -> Option<Self> {
531 Some(Self {
532 public_group: public_group?,
533 group_epoch_secrets: group_epoch_secrets?,
534 own_leaf_index: own_leaf_index?,
535 message_secrets_store: message_secrets_store?,
536 resumption_psk_store: resumption_psk_store?,
537 mls_group_config: mls_group_config?,
538 own_leaf_nodes,
539 aad: vec![],
540 #[cfg(feature = "extensions-draft")]
541 safe_aad: SafeAad::empty(),
542 group_state: group_state?,
543 #[cfg(feature = "extensions-draft")]
544 application_export_tree,
545 #[cfg(feature = "virtual-clients-draft")]
546 emulation_group,
547 })
548 };
549
550 Ok(build())
551 }
552
553 pub fn delete<Storage: crate::storage::StorageProvider>(
557 &mut self,
558 storage: &Storage,
559 ) -> Result<(), Storage::Error> {
560 PublicGroup::delete(storage, self.group_id())?;
561 storage.delete_own_leaf_index(self.group_id())?;
562 storage.delete_group_epoch_secrets(self.group_id())?;
563 storage.delete_message_secrets(self.group_id())?;
564 storage.delete_all_resumption_psk_secrets(self.group_id())?;
565 storage.delete_group_config(self.group_id())?;
566 storage.delete_own_leaf_nodes(self.group_id())?;
567 storage.delete_group_state(self.group_id())?;
568 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
569
570 #[cfg(feature = "extensions-draft")]
571 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
572
573 #[cfg(feature = "virtual-clients-draft")]
578 {
579 storage.delete_vc_emulation_bindings(self.group_id())?;
580 storage.delete_registered_vc_derivation_epoch(self.group_id())?;
581 }
582
583 self.proposal_store_mut().empty();
584 storage.delete_encryption_epoch_key_pairs(
585 self.group_id(),
586 &self.epoch(),
587 self.own_leaf_index().u32(),
588 )?;
589
590 Ok(())
591 }
592
593 pub fn export_ratchet_tree(&self) -> RatchetTree {
597 self.public_group().export_ratchet_tree()
598 }
599}
600
601#[cfg(feature = "virtual-clients-draft")]
607#[derive(thiserror::Error, Debug, PartialEq, Clone)]
608pub(crate) enum VcDerivationStateError<StorageError> {
609 #[error("Error reading the binding or derivation-epoch state from storage: {0}")]
611 Storage(StorageError),
612 #[error("The group is bound to a derivation epoch, but its state is missing.")]
614 MissingDerivationEpochState,
615}
616
617impl MlsGroup {
619 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
621 self.public_group.required_capabilities()
622 }
623
624 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
626 &self.group_epoch_secrets
627 }
628
629 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
631 self.message_secrets_store.message_secrets()
632 }
633
634 pub(crate) fn resize_message_secrets_store(&mut self, policy: &PastEpochDeletionPolicy) {
638 self.message_secrets_store.resize(policy);
639 }
640
641 pub fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
643 self.mls_group_config.past_epoch_deletion_policy()
644 }
645
646 pub fn set_past_epoch_deletion_policy<Provider: OpenMlsProvider>(
648 &mut self,
649 provider: &Provider,
650 policy: PastEpochDeletionPolicy,
651 ) -> Result<(), SetPastEpochDeletionPolicyError<Provider::StorageError>> {
652 self.resize_message_secrets_store(&policy);
654
655 self.mls_group_config.past_epoch_deletion_policy = policy;
657
658 provider
660 .storage()
661 .write_mls_join_config(self.group_id(), &self.mls_group_config)?;
662
663 provider
665 .storage()
666 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
667
668 Ok(())
669 }
670
671 pub(crate) fn message_secrets_for_epoch_mut(
673 &mut self,
674 epoch: GroupEpoch,
675 ) -> Result<&mut MessageSecrets, SecretTreeError> {
676 if epoch < self.context().epoch() {
677 self.message_secrets_store
678 .secrets_for_epoch_mut(epoch)
679 .ok_or(SecretTreeError::TooDistantInThePast)
680 } else {
681 Ok(self.message_secrets_store.message_secrets_mut())
682 }
683 }
684
685 pub(crate) fn message_secrets_for_epoch(
687 &self,
688 epoch: GroupEpoch,
689 ) -> Result<&MessageSecrets, SecretTreeError> {
690 if epoch < self.context().epoch() {
691 self.message_secrets_store
692 .secrets_for_epoch(epoch)
693 .ok_or(SecretTreeError::TooDistantInThePast)
694 } else {
695 Ok(self.message_secrets_store.message_secrets())
696 }
697 }
698
699 pub(crate) fn message_secrets_and_leaves(
705 &self,
706 epoch: GroupEpoch,
707 ) -> Result<(&MessageSecrets, &[Member]), SecretTreeError> {
708 if epoch < self.context().epoch() {
709 self.message_secrets_store
710 .secrets_and_leaves_for_epoch(epoch)
711 .ok_or(SecretTreeError::TooDistantInThePast)
712 } else {
713 Ok((self.message_secrets_store.message_secrets(), &[]))
716 }
717 }
718
719 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
721 &self,
722 framing_parameters: FramingParameters,
723 extensions: Extensions<GroupContext>,
724 signer: &impl Signer,
725 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
726 {
727 let required_extension = extensions
729 .iter()
730 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
731 if let Some(required_extension) = required_extension {
732 let required_capabilities = required_extension.as_required_capabilities_extension()?;
733 self.own_leaf_node()
735 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
736 .capabilities()
737 .supports_required_capabilities(required_capabilities)?;
738
739 self.public_group()
742 .check_extension_support(required_capabilities.extension_types())?;
743 }
744 let proposal = GroupContextExtensionProposal::new(extensions);
745 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
746 AuthenticatedContent::member_proposal(
747 framing_parameters,
748 self.own_leaf_index(),
749 proposal,
750 self.context(),
751 signer,
752 )
753 .map_err(|e| e.into())
754 }
755
756 #[cfg(feature = "virtual-clients-draft")]
764 pub(crate) fn vc_derivation_state_at_epoch<Storage: StorageProvider>(
765 &self,
766 storage: &Storage,
767 epoch: GroupEpoch,
768 ) -> Result<
769 Option<crate::components::vc_derivation_info::VcDerivationEpochState>,
770 VcDerivationStateError<Storage::Error>,
771 > {
772 let bindings: Option<crate::components::vc_derivation_info::VcEmulationBindings> = storage
773 .vc_emulation_bindings(self.group_id())
774 .map_err(VcDerivationStateError::Storage)?;
775 let Some(epoch_id) = bindings.and_then(|bindings| bindings.get(epoch).cloned()) else {
776 return Ok(None);
777 };
778 let state = storage
779 .vc_derivation_epoch_state(&epoch_id)
780 .map_err(VcDerivationStateError::Storage)?
781 .ok_or_else(|| {
782 log::error!("vc: group is bound to derivation epoch, but state is missing");
783 VcDerivationStateError::MissingDerivationEpochState
784 })?;
785 Ok(Some(state))
786 }
787
788 #[cfg(feature = "virtual-clients-draft")]
796 pub fn is_emulation_group(&self) -> bool {
797 self.emulation_group
798 }
799
800 #[cfg(feature = "virtual-clients-draft")]
815 pub fn newest_vc_derivation_epoch<Storage: StorageProvider>(
816 &self,
817 storage: &Storage,
818 ) -> Result<Option<crate::components::vc_derivation_info::EpochId>, Storage::Error> {
819 crate::components::vc_derivation_info::newest_vc_derivation_epoch(storage, self.group_id())
820 }
821
822 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
824 &mut self,
825 public_message: AuthenticatedContent,
826 provider: &Provider,
827 ) -> Result<EncryptionOutput, MessageEncryptionError<Provider::StorageError>> {
828 let padding_size = self.configuration().padding_size();
829
830 #[cfg(feature = "virtual-clients-draft")]
834 let derivation_state = self
835 .vc_derivation_state_at_epoch(provider.storage(), self.epoch())
836 .map_err(|e| match e {
837 VcDerivationStateError::Storage(e) => MessageEncryptionError::StorageError(e),
838 VcDerivationStateError::MissingDerivationEpochState => {
839 MessageEncryptionError::VirtualClientsError(
840 crate::components::vc_derivation_info::VirtualClientsError::MissingDerivationEpochState,
841 )
842 }
843 })?;
844 #[cfg(feature = "virtual-clients-draft")]
845 let emulator_ctx: Option<crate::framing::EmulatorReuseGuardCtx<'_>> = derivation_state
846 .as_ref()
847 .map(|state| state.reuse_guard_inputs());
848
849 let msg = PrivateMessage::try_from_authenticated_content(
850 provider.crypto(),
851 provider.rand(),
852 &public_message,
853 self.ciphersuite(),
854 self.message_secrets_store.message_secrets_mut(),
855 padding_size,
856 #[cfg(feature = "virtual-clients-draft")]
857 emulator_ctx.as_ref(),
858 )?;
859
860 #[cfg(feature = "virtual-clients-draft")]
865 let msg = {
866 use crate::components::vc_derivation_info::RatchetType;
867 let mut msg = msg;
868 if let Some(state) = &derivation_state {
869 let ratchet_type = match public_message.content().content_type() {
870 ContentType::Application => RatchetType::Application,
871 ContentType::Proposal | ContentType::Commit => RatchetType::Handshake,
872 };
873 let generation_id = state
874 .derive_generation_id(
875 provider.crypto(),
876 self.group_id(),
877 self.epoch(),
878 msg.generation,
879 ratchet_type,
880 )
881 .map_err(MessageEncryptionError::VirtualClientsError)?;
882 msg.generation_id = Some(generation_id);
883 }
884 msg
885 };
886
887 provider
888 .storage()
889 .write_message_secrets(self.group_id(), &self.message_secrets_store)
890 .map_err(MessageEncryptionError::StorageError)?;
891
892 Ok(msg)
893 }
894
895 pub(crate) fn outgoing_wire_format(&self) -> WireFormat {
897 self.mls_group_config.wire_format_policy().outgoing().into()
898 }
899
900 pub(crate) fn outgoing_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
906 #[cfg(feature = "extensions-draft")]
907 {
908 self.assembled_authenticated_data()
909 }
910 #[cfg(not(feature = "extensions-draft"))]
911 {
912 Ok(self.aad.clone())
913 }
914 }
915
916 #[cfg(feature = "extensions-draft")]
921 pub(crate) fn assembled_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
922 if !self.context().safe_aad_required() {
923 return Ok(self.aad.clone());
924 }
925 crate::framing::safe_aad::assemble_authenticated_data(&self.safe_aad, &self.aad)
926 .map_err(|_| LibraryError::custom("SafeAad serialization failed"))
927 }
928
929 pub fn delete_past_epoch_secrets<Provider: OpenMlsProvider>(
933 &mut self,
934 provider: &Provider,
935 policy: PastEpochDeletion,
936 ) -> Result<(), DeletePastEpochSecretsError<Provider::StorageError>> {
937 self.message_secrets_store.delete_past_epoch_secrets(policy);
939 provider
941 .storage()
942 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
943
944 Ok(())
945 }
946
947 pub fn proposal_store(&self) -> &ProposalStore {
949 self.public_group.proposal_store()
950 }
951
952 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
954 self.public_group.proposal_store_mut()
955 }
956
957 pub(crate) fn context(&self) -> &GroupContext {
959 self.public_group.group_context()
960 }
961
962 pub(crate) fn version(&self) -> ProtocolVersion {
964 self.public_group.version()
965 }
966
967 #[inline]
969 pub(crate) fn reset_aad(&mut self) {
970 self.aad.clear();
971 #[cfg(feature = "extensions-draft")]
972 {
973 self.safe_aad = SafeAad::empty();
974 }
975 }
976
977 pub fn public_group(&self) -> &PublicGroup {
979 &self.public_group
980 }
981}
982
983#[cfg(feature = "virtual-clients-draft")]
994#[derive(Debug, Clone)]
995pub struct HandshakeConfirmationData {
996 pub epoch: GroupEpoch,
999 pub generation: u32,
1001 pub generation_id: Option<crate::components::vc_derivation_info::GenerationId>,
1006}
1007
1008pub(crate) struct HandshakeFramingOutput {
1016 pub(crate) message: MlsMessageOut,
1017 #[cfg(feature = "virtual-clients-draft")]
1018 pub(crate) confirmation: Option<HandshakeConfirmationData>,
1019}
1020
1021impl MlsGroup {
1023 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
1028 &self,
1029 store: &Storage,
1030 keypair_references: &[EncryptionKeyPair],
1031 ) -> Result<(), Storage::Error> {
1032 store.write_encryption_epoch_key_pairs(
1033 self.group_id(),
1034 &self.context().epoch(),
1035 self.own_leaf_index().u32(),
1036 keypair_references,
1037 )
1038 }
1039
1040 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
1045 &self,
1046 store: &Storage,
1047 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
1048 store.encryption_epoch_key_pairs(
1049 self.group_id(),
1050 &self.context().epoch(),
1051 self.own_leaf_index().u32(),
1052 )
1053 }
1054
1055 #[cfg(not(feature = "virtual-clients-draft"))]
1060 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
1061 &self,
1062 store: &Storage,
1063 ) -> Result<(), Storage::Error> {
1064 store.delete_encryption_epoch_key_pairs(
1065 self.group_id(),
1066 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
1067 self.own_leaf_index().u32(),
1068 )
1069 }
1070
1071 #[cfg(feature = "virtual-clients-draft")]
1072 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
1073 &self,
1074 store: &Storage,
1075 previous_own_leaf_index: LeafNodeIndex,
1076 ) -> Result<(), Storage::Error> {
1077 store.delete_encryption_epoch_key_pairs(
1083 self.group_id(),
1084 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
1085 previous_own_leaf_index.u32(),
1086 )
1087 }
1088
1089 pub(super) fn store<Storage: crate::storage::StorageProvider>(
1092 &self,
1093 storage: &Storage,
1094 ) -> Result<(), Storage::Error> {
1095 self.public_group.store(storage)?;
1096 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
1097 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
1098 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
1099 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
1100 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
1101 storage.write_group_state(self.group_id(), &self.group_state)?;
1102 #[cfg(feature = "extensions-draft")]
1103 if let Some(application_export_tree) = &self.application_export_tree {
1104 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
1105 }
1106
1107 Ok(())
1108 }
1109
1110 fn content_to_mls_message(
1114 &mut self,
1115 mls_auth_content: AuthenticatedContent,
1116 provider: &impl OpenMlsProvider,
1117 ) -> Result<HandshakeFramingOutput, LibraryError> {
1118 let output = match self.configuration().wire_format_policy().outgoing() {
1119 OutgoingWireFormatPolicy::AlwaysPlaintext => {
1120 let mut plaintext: PublicMessage = mls_auth_content.into();
1121 if plaintext.sender().is_member() {
1123 plaintext.set_membership_tag(
1124 provider.crypto(),
1125 self.ciphersuite(),
1126 self.message_secrets().membership_key(),
1127 self.message_secrets().serialized_context(),
1128 )?;
1129 }
1130 HandshakeFramingOutput {
1131 message: plaintext.into(),
1132 #[cfg(feature = "virtual-clients-draft")]
1133 confirmation: None,
1134 }
1135 }
1136 OutgoingWireFormatPolicy::AlwaysCiphertext => {
1137 #[cfg(feature = "virtual-clients-draft")]
1141 let epoch = self.epoch();
1142 let encryption_output = self
1143 .encrypt(mls_auth_content, provider)
1144 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
1146 let message = MlsMessageOut::from_private_message(
1147 encryption_output.private_message,
1148 self.version(),
1149 );
1150 HandshakeFramingOutput {
1151 message,
1152 #[cfg(feature = "virtual-clients-draft")]
1153 confirmation: Some(HandshakeConfirmationData {
1154 epoch,
1155 generation: encryption_output.generation,
1156 generation_id: encryption_output.generation_id,
1157 }),
1158 }
1159 }
1160 };
1161 Ok(output)
1162 }
1163
1164 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
1167 match self.group_state {
1168 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
1169 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
1170 MlsGroupState::Operational => Ok(()),
1171 }
1172 }
1173}
1174
1175impl MlsGroup {
1177 #[cfg(any(feature = "test-utils", test))]
1178 pub fn export_group_context(&self) -> &GroupContext {
1179 self.context()
1180 }
1181
1182 #[cfg(any(feature = "test-utils", test))]
1183 pub fn tree_hash(&self) -> &[u8] {
1184 self.public_group().group_context().tree_hash()
1185 }
1186
1187 #[cfg(any(feature = "test-utils", test))]
1188 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
1189 self.message_secrets_store.message_secrets_mut()
1190 }
1191
1192 #[cfg(any(feature = "test-utils", test))]
1193 pub fn print_ratchet_tree(&self, message: &str) {
1194 println!("{}: {}", message, self.public_group().export_ratchet_tree());
1195 }
1196
1197 #[cfg(any(feature = "test-utils", test))]
1198 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
1199 self.public_group.context_mut()
1200 }
1201
1202 #[cfg(test)]
1203 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
1204 self.own_leaf_index = own_leaf_index;
1205 }
1206
1207 #[cfg(test)]
1208 pub(crate) fn own_tree_position(&self) -> TreePosition {
1209 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
1210 }
1211
1212 #[cfg(test)]
1213 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
1214 &self.message_secrets_store
1215 }
1216
1217 #[cfg(test)]
1218 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
1219 &self.resumption_psk_store
1220 }
1221
1222 #[cfg(test)]
1223 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
1224 self.public_group.set_group_context(group_context)
1225 }
1226
1227 #[cfg(any(test, feature = "test-utils"))]
1228 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
1229 let loaded = MlsGroup::load(storage, self.group_id())
1230 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
1231 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
1232
1233 if self != &other {
1234 let mut diagnostics = Vec::new();
1235
1236 if self.mls_group_config != other.mls_group_config {
1237 diagnostics.push(format!(
1238 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
1239 self.mls_group_config, other.mls_group_config
1240 ));
1241 }
1242 if self.public_group != other.public_group {
1243 diagnostics.push(format!(
1244 "public_group:\n Current: {:?}\n Loaded: {:?}",
1245 self.public_group, other.public_group
1246 ));
1247 }
1248 if self.group_epoch_secrets != other.group_epoch_secrets {
1249 diagnostics.push(format!(
1250 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
1251 self.group_epoch_secrets, other.group_epoch_secrets
1252 ));
1253 }
1254 if self.own_leaf_index != other.own_leaf_index {
1255 diagnostics.push(format!(
1256 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
1257 self.own_leaf_index, other.own_leaf_index
1258 ));
1259 }
1260 if self.message_secrets_store != other.message_secrets_store {
1261 diagnostics.push(format!(
1262 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
1263 self.message_secrets_store, other.message_secrets_store
1264 ));
1265 }
1266 if self.resumption_psk_store != other.resumption_psk_store {
1267 diagnostics.push(format!(
1268 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
1269 self.resumption_psk_store, other.resumption_psk_store
1270 ));
1271 }
1272 if self.own_leaf_nodes != other.own_leaf_nodes {
1273 diagnostics.push(format!(
1274 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
1275 self.own_leaf_nodes, other.own_leaf_nodes
1276 ));
1277 }
1278 if self.aad != other.aad {
1279 diagnostics.push(format!(
1280 "aad:\n Current: {:?}\n Loaded: {:?}",
1281 self.aad, other.aad
1282 ));
1283 }
1284 if self.group_state != other.group_state {
1285 diagnostics.push(format!(
1286 "group_state:\n Current: {:?}\n Loaded: {:?}",
1287 self.group_state, other.group_state
1288 ));
1289 }
1290 #[cfg(feature = "extensions-draft")]
1291 if self.application_export_tree != other.application_export_tree {
1292 diagnostics.push(format!(
1293 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
1294 self.application_export_tree, other.application_export_tree
1295 ));
1296 }
1297 #[cfg(feature = "virtual-clients-draft")]
1298 if self.emulation_group != other.emulation_group {
1299 diagnostics.push(format!(
1300 "emulation_group:\n Current: {:?}\n Loaded: {:?}",
1301 self.emulation_group, other.emulation_group
1302 ));
1303 }
1304
1305 log::error!(
1306 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
1307 diagnostics.len(),
1308 diagnostics.join("\n\n")
1309 );
1310
1311 return Err(LibraryError::custom(
1312 "Loaded group does not match current group",
1313 ));
1314 }
1315
1316 Ok(())
1317 }
1318}
1319
1320#[derive(Debug)]
1323pub struct StagedWelcome {
1324 mls_group_config: MlsGroupJoinConfig,
1326 public_group: PublicGroup,
1327 group_epoch_secrets: GroupEpochSecrets,
1328 own_leaf_index: LeafNodeIndex,
1329
1330 message_secrets_store: MessageSecretsStore,
1337
1338 #[cfg(feature = "extensions-draft")]
1341 application_export_secret: ApplicationExportSecret,
1342
1343 resumption_psk_store: ResumptionPskStore,
1345
1346 verifiable_group_info: VerifiableGroupInfo,
1348
1349 key_material: WelcomeKeyMaterial,
1351
1352 path_keypairs: Option<Vec<EncryptionKeyPair>>,
1354
1355 #[cfg(feature = "virtual-clients-draft")]
1358 emulation_group: bool,
1359}
1360
1361pub struct ProcessedWelcome {
1368 mls_group_config: MlsGroupJoinConfig,
1370
1371 ciphersuite: Ciphersuite,
1374 group_secrets: GroupSecrets,
1375 epoch_secrets: crate::schedule::EpochSecretsResult,
1376 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
1377 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
1378 key_material: WelcomeKeyMaterial,
1379}
1380
1381#[derive(Debug)]
1383pub struct WelcomeKeyMaterial {
1384 inner: WelcomeKeyMaterialInner,
1385}
1386
1387#[derive(Debug)]
1396pub(crate) enum WelcomeKeyMaterialInner {
1397 KeyPackage(Box<KeyPackageBundle>),
1400 #[cfg(feature = "virtual-clients-draft")]
1403 VirtualClient(crate::components::vc_derivation_info::VcWelcomeMaterial),
1404}
1405
1406impl WelcomeKeyMaterial {
1407 pub(crate) fn with_key_package_bundle(key_package: KeyPackageBundle) -> Self {
1409 Self {
1410 inner: WelcomeKeyMaterialInner::KeyPackage(Box::new(key_package)),
1411 }
1412 }
1413
1414 #[cfg(feature = "virtual-clients-draft")]
1418 pub(crate) fn with_vc_welcome_material(
1419 material: crate::components::vc_derivation_info::VcWelcomeMaterial,
1420 ) -> Self {
1421 Self {
1422 inner: WelcomeKeyMaterialInner::VirtualClient(material),
1423 }
1424 }
1425
1426 pub(crate) fn inner(&self) -> &WelcomeKeyMaterialInner {
1427 &self.inner
1428 }
1429
1430 pub fn key_package_ref(
1436 &self,
1437 crypto: &impl OpenMlsCrypto,
1438 ) -> Result<crate::ciphersuite::hash_ref::KeyPackageRef, LibraryError> {
1439 match &self.inner {
1440 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.key_package().hash_ref(crypto),
1441 #[cfg(feature = "virtual-clients-draft")]
1442 WelcomeKeyMaterialInner::VirtualClient(material) => {
1443 Ok(material.key_package_ref.clone())
1444 }
1445 }
1446 }
1447
1448 pub fn init_private_key(&self) -> &crate::ciphersuite::HpkePrivateKey {
1450 match &self.inner {
1451 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.init_private_key(),
1452 #[cfg(feature = "virtual-clients-draft")]
1453 WelcomeKeyMaterialInner::VirtualClient(material) => &material.init_private_key,
1454 }
1455 }
1456
1457 pub fn hpke_init_key(&self) -> &InitKey {
1459 match &self.inner {
1460 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.key_package().hpke_init_key(),
1461 #[cfg(feature = "virtual-clients-draft")]
1462 WelcomeKeyMaterialInner::VirtualClient(material) => &material.init_key,
1463 }
1464 }
1465
1466 pub fn key_package_bundle(&self) -> Option<&KeyPackageBundle> {
1470 match &self.inner {
1471 WelcomeKeyMaterialInner::KeyPackage(bundle) => Some(bundle),
1472 #[cfg(feature = "virtual-clients-draft")]
1473 WelcomeKeyMaterialInner::VirtualClient(_) => None,
1474 }
1475 }
1476
1477 fn encryption_key_pair(&self) -> EncryptionKeyPair {
1479 match &self.inner {
1480 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.encryption_key_pair(),
1481 #[cfg(feature = "virtual-clients-draft")]
1482 WelcomeKeyMaterialInner::VirtualClient(material) => material.encryption_keypair.clone(),
1483 }
1484 }
1485}