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
53#[cfg(all(feature = "virtual-clients-draft", not(target_arch = "wasm32")))]
54use std::time::SystemTime;
55
56#[cfg(all(feature = "virtual-clients-draft", target_arch = "wasm32"))]
57use web_time::SystemTime;
58
59#[cfg(feature = "virtual-clients-draft")]
60use crate::group::{
61 VcDerivationEpochDeletion, VcDerivationEpochDeletionResult, VcDerivationEpochDeletionTime,
62 VcDerivationEpochRetentionPolicy,
63};
64
65mod application;
67mod exporting;
68mod updates;
69
70#[cfg(feature = "migration-import")]
71pub(crate) mod migration_import;
72
73#[cfg(feature = "virtual-clients-draft")]
74pub use application::UnconfirmedMessage;
75pub use branch::BranchInfo;
76pub use exporting::{
77 ExportedSecret, GroupExport, ProcessedWelcomeExport, StagedCommitExport, StagedWelcomeExport,
78};
79#[cfg(feature = "extensions-draft")]
80pub use exporting::{GroupSafeExport, PendingSafeExport, StagedCommitSafeExport};
81pub use proposal::Propose;
82
83use config::*;
84
85pub(crate) mod branch;
87pub(crate) mod builder;
88pub(crate) mod commit_builder;
89pub(crate) mod config;
90pub(crate) mod creation;
91pub(crate) mod errors;
92pub(crate) mod membership;
93pub(crate) mod past_secrets;
94pub(crate) mod processing;
95pub(crate) mod proposal;
96pub(crate) mod proposal_store;
97pub(crate) mod staged_commit;
98
99#[cfg(feature = "extensions-draft")]
100pub(crate) mod app_ephemeral;
101
102#[cfg(feature = "targeted-messages-draft")]
103mod targeted_messages;
104
105#[cfg(feature = "virtual-clients-draft")]
106mod vc_application_secret;
107
108#[cfg(test)]
110pub(crate) mod tests_and_kats;
111
112#[derive(Debug)]
113pub(crate) struct CreateCommitResult {
114 pub(crate) commit: AuthenticatedContent,
115 pub(crate) welcome_option: Option<Welcome>,
116 pub(crate) staged_commit: StagedCommit,
117 pub(crate) group_info: Option<GroupInfo>,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122pub struct Member {
123 pub index: LeafNodeIndex,
125 pub credential: Credential,
127 pub encryption_key: Vec<u8>,
129 pub signature_key: Vec<u8>,
131}
132
133impl Member {
134 pub fn new(
136 index: LeafNodeIndex,
137 encryption_key: Vec<u8>,
138 signature_key: Vec<u8>,
139 credential: Credential,
140 ) -> Self {
141 Self {
142 index,
143 encryption_key,
144 signature_key,
145 credential,
146 }
147 }
148}
149
150#[derive(Debug, Serialize, Deserialize)]
153#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
154pub enum PendingCommitState {
155 Member(StagedCommit),
157 External(StagedCommit),
159}
160
161impl PendingCommitState {
162 pub(crate) fn staged_commit(&self) -> &StagedCommit {
165 match self {
166 PendingCommitState::Member(pc) => pc,
167 PendingCommitState::External(pc) => pc,
168 }
169 }
170}
171
172impl From<PendingCommitState> for StagedCommit {
173 fn from(pcs: PendingCommitState) -> Self {
174 match pcs {
175 PendingCommitState::Member(pc) => pc,
176 PendingCommitState::External(pc) => pc,
177 }
178 }
179}
180
181#[derive(Debug, Serialize, Deserialize)]
226#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
227pub enum MlsGroupState {
228 PendingCommit(Box<PendingCommitState>),
230 Operational,
232 Inactive,
234}
235
236#[derive(Debug)]
261#[cfg_attr(feature = "migration-import", derive(serde::Deserialize))]
262#[cfg_attr(
263 all(feature = "migration-import", feature = "test-utils"),
264 derive(serde::Serialize)
265)]
266#[cfg_attr(feature = "test-utils", derive(Clone, PartialEq))]
267pub struct MlsGroup {
268 mls_group_config: MlsGroupJoinConfig,
270 public_group: PublicGroup,
272 group_epoch_secrets: GroupEpochSecrets,
274 own_leaf_index: LeafNodeIndex,
276 message_secrets_store: MessageSecretsStore,
283 resumption_psk_store: ResumptionPskStore,
285 own_leaf_nodes: Vec<LeafNode>,
289 aad: Vec<u8>,
293 #[cfg(feature = "extensions-draft")]
297 #[cfg_attr(
301 feature = "migration-import",
302 serde(default = "crate::framing::SafeAad::empty")
303 )]
304 safe_aad: SafeAad,
305 group_state: MlsGroupState,
308 #[cfg(feature = "extensions-draft")]
312 #[cfg_attr(feature = "migration-import", serde(default))]
316 application_export_tree: Option<ApplicationExportTree>,
317 #[cfg(feature = "virtual-clients-draft")]
321 #[cfg_attr(feature = "migration-import", serde(default))]
324 emulation_group: bool,
325}
326
327impl MlsGroup {
328 pub fn configuration(&self) -> &MlsGroupJoinConfig {
332 &self.mls_group_config
333 }
334
335 pub fn set_configuration<Storage: StorageProvider>(
337 &mut self,
338 storage: &Storage,
339 mls_group_config: &MlsGroupJoinConfig,
340 ) -> Result<(), Storage::Error> {
341 let policy_changed = self.mls_group_config.past_epoch_deletion_policy()
342 != mls_group_config.past_epoch_deletion_policy();
343 #[cfg(feature = "virtual-clients-draft")]
344 let retention_changed = self.mls_group_config.vc_derivation_epoch_retention_policy()
345 != mls_group_config.vc_derivation_epoch_retention_policy();
346
347 self.mls_group_config = mls_group_config.clone();
348 storage.write_mls_join_config(self.group_id(), mls_group_config)?;
349
350 if policy_changed {
351 self.resize_message_secrets_store(mls_group_config.past_epoch_deletion_policy());
353 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
354 }
355
356 #[cfg(feature = "virtual-clients-draft")]
357 if retention_changed {
358 self.apply_vc_derivation_epoch_retention(storage)?;
359 }
360
361 Ok(())
362 }
363
364 pub fn set_aad(&mut self, aad: Vec<u8>) {
368 self.aad = aad;
369 }
370
371 pub fn aad(&self) -> &[u8] {
374 &self.aad
375 }
376
377 #[cfg(feature = "extensions-draft")]
387 pub fn set_safe_aad(&mut self, items: Vec<SafeAadItem>) -> Result<(), SafeAadError> {
388 self.safe_aad = SafeAad::from_items(items)?;
389 Ok(())
390 }
391
392 #[cfg(feature = "extensions-draft")]
395 pub fn safe_aad_items(&self) -> &[SafeAadItem] {
396 self.safe_aad.items()
397 }
398
399 pub fn ciphersuite(&self) -> Ciphersuite {
403 self.public_group.ciphersuite()
404 }
405
406 pub fn confirmation_tag(&self) -> &ConfirmationTag {
408 self.public_group.confirmation_tag()
409 }
410
411 pub fn is_active(&self) -> bool {
414 !matches!(self.group_state, MlsGroupState::Inactive)
415 }
416
417 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
420 if !self.is_active() {
421 return Err(MlsGroupStateError::UseAfterEviction);
422 }
423 self.public_group
424 .leaf(self.own_leaf_index())
425 .map(|node| node.credential())
426 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
427 }
428
429 pub fn own_leaf_index(&self) -> LeafNodeIndex {
431 self.own_leaf_index
432 }
433
434 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
436 self.public_group().leaf(self.own_leaf_index())
437 }
438
439 pub fn group_id(&self) -> &GroupId {
441 self.public_group.group_id()
442 }
443
444 pub fn epoch(&self) -> GroupEpoch {
446 self.public_group.group_context().epoch()
447 }
448
449 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
451 self.proposal_store().proposals()
452 }
453
454 pub fn treesync(&self) -> &TreeSync {
456 self.public_group.treesync()
457 }
458
459 pub fn pending_commit(&self) -> Option<&StagedCommit> {
463 match self.group_state {
464 MlsGroupState::PendingCommit(ref pending_commit_state) => {
465 Some(pending_commit_state.staged_commit())
466 }
467 MlsGroupState::Operational => None,
468 MlsGroupState::Inactive => None,
469 }
470 }
471
472 pub fn clear_pending_commit<Storage: StorageProvider>(
484 &mut self,
485 storage: &Storage,
486 ) -> Result<(), Storage::Error> {
487 match self.group_state {
488 MlsGroupState::PendingCommit(ref pending_commit_state) => {
489 if let PendingCommitState::Member(_) = **pending_commit_state {
490 self.group_state = MlsGroupState::Operational;
491 storage.write_group_state(self.group_id(), &self.group_state)
492 } else {
493 Ok(())
494 }
495 }
496 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
497 }
498 }
499
500 pub fn clear_pending_proposals<Storage: StorageProvider>(
507 &mut self,
508 storage: &Storage,
509 ) -> Result<(), Storage::Error> {
510 if !self.proposal_store().is_empty() {
512 self.proposal_store_mut().empty();
514
515 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
517 }
518
519 Ok(())
520 }
521
522 pub fn extensions(&self) -> &Extensions<GroupContext> {
524 self.public_group().group_context().extensions()
525 }
526
527 pub fn ext_commit_sender_index(
529 &self,
530 commit: &StagedCommit,
531 ) -> Result<LeafNodeIndex, LibraryError> {
532 self.public_group().ext_commit_sender_index(commit)
533 }
534
535 pub fn load<Storage: crate::storage::StorageProvider>(
539 storage: &Storage,
540 group_id: &GroupId,
541 ) -> Result<Option<MlsGroup>, Storage::Error> {
542 let public_group = PublicGroup::load(storage, group_id)?;
543 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
544 let own_leaf_index = storage.own_leaf_index(group_id)?;
545 let message_secrets_store = storage.message_secrets(group_id)?;
546 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
547 let mls_group_config = storage.mls_group_join_config(group_id)?;
548 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
549 let group_state = storage.group_state(group_id)?;
550 #[cfg(feature = "extensions-draft")]
551 let application_export_tree = storage.application_export_tree(group_id)?;
552 #[cfg(feature = "virtual-clients-draft")]
556 let emulation_group =
557 crate::components::vc_derivation_info::newest_vc_derivation_epoch(storage, group_id)?
558 .is_some();
559
560 let build = || -> Option<Self> {
561 Some(Self {
562 public_group: public_group?,
563 group_epoch_secrets: group_epoch_secrets?,
564 own_leaf_index: own_leaf_index?,
565 message_secrets_store: message_secrets_store?,
566 resumption_psk_store: resumption_psk_store?,
567 mls_group_config: mls_group_config?,
568 own_leaf_nodes,
569 aad: vec![],
570 #[cfg(feature = "extensions-draft")]
571 safe_aad: SafeAad::empty(),
572 group_state: group_state?,
573 #[cfg(feature = "extensions-draft")]
574 application_export_tree,
575 #[cfg(feature = "virtual-clients-draft")]
576 emulation_group,
577 })
578 };
579
580 Ok(build())
581 }
582
583 pub fn delete<Storage: crate::storage::StorageProvider>(
587 &mut self,
588 storage: &Storage,
589 ) -> Result<(), Storage::Error> {
590 PublicGroup::delete(storage, self.group_id())?;
591 storage.delete_own_leaf_index(self.group_id())?;
592 storage.delete_group_epoch_secrets(self.group_id())?;
593 storage.delete_message_secrets(self.group_id())?;
594 storage.delete_all_resumption_psk_secrets(self.group_id())?;
595 storage.delete_group_config(self.group_id())?;
596 storage.delete_own_leaf_nodes(self.group_id())?;
597 storage.delete_group_state(self.group_id())?;
598 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
599
600 #[cfg(feature = "extensions-draft")]
601 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
602
603 #[cfg(feature = "virtual-clients-draft")]
606 self.drop_all_vc_derivation_epoch_references(storage)?;
607
608 self.proposal_store_mut().empty();
609 storage.delete_encryption_epoch_key_pairs(
610 self.group_id(),
611 &self.epoch(),
612 self.own_leaf_index().u32(),
613 )?;
614
615 Ok(())
616 }
617
618 pub fn export_ratchet_tree(&self) -> RatchetTree {
622 self.public_group().export_ratchet_tree()
623 }
624}
625
626#[cfg(feature = "virtual-clients-draft")]
632#[derive(thiserror::Error, Debug, PartialEq, Clone)]
633pub(crate) enum VcDerivationStateError<StorageError> {
634 #[error("Error reading the binding or derivation-epoch state from storage: {0}")]
636 Storage(StorageError),
637 #[error("The group is bound to a derivation epoch, but its state is missing.")]
639 MissingDerivationEpochState,
640}
641
642impl MlsGroup {
644 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
646 self.public_group.required_capabilities()
647 }
648
649 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
651 &self.group_epoch_secrets
652 }
653
654 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
656 self.message_secrets_store.message_secrets()
657 }
658
659 pub(crate) fn resize_message_secrets_store(&mut self, policy: &PastEpochDeletionPolicy) {
663 self.message_secrets_store.resize(policy);
664 }
665
666 pub fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
668 self.mls_group_config.past_epoch_deletion_policy()
669 }
670
671 pub fn set_past_epoch_deletion_policy<Provider: OpenMlsProvider>(
673 &mut self,
674 provider: &Provider,
675 policy: PastEpochDeletionPolicy,
676 ) -> Result<(), SetPastEpochDeletionPolicyError<Provider::StorageError>> {
677 self.resize_message_secrets_store(&policy);
679
680 self.mls_group_config.past_epoch_deletion_policy = policy;
682
683 provider
685 .storage()
686 .write_mls_join_config(self.group_id(), &self.mls_group_config)?;
687
688 provider
690 .storage()
691 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
692
693 Ok(())
694 }
695
696 #[cfg(feature = "virtual-clients-draft")]
698 pub fn vc_derivation_epoch_retention_policy(&self) -> &VcDerivationEpochRetentionPolicy {
699 self.mls_group_config.vc_derivation_epoch_retention_policy()
700 }
701
702 #[cfg(feature = "virtual-clients-draft")]
705 pub fn set_vc_derivation_epoch_retention_policy<Provider: OpenMlsProvider>(
706 &mut self,
707 provider: &Provider,
708 policy: VcDerivationEpochRetentionPolicy,
709 ) -> Result<(), Provider::StorageError> {
710 self.mls_group_config.vc_derivation_epoch_retention_policy = policy;
711 provider
712 .storage()
713 .write_mls_join_config(self.group_id(), &self.mls_group_config)?;
714 self.apply_vc_derivation_epoch_retention(provider.storage())
715 }
716
717 pub(crate) fn message_secrets_for_epoch_mut(
719 &mut self,
720 epoch: GroupEpoch,
721 ) -> Result<&mut MessageSecrets, SecretTreeError> {
722 if epoch < self.context().epoch() {
723 self.message_secrets_store
724 .secrets_for_epoch_mut(epoch)
725 .ok_or(SecretTreeError::TooDistantInThePast)
726 } else {
727 Ok(self.message_secrets_store.message_secrets_mut())
728 }
729 }
730
731 pub(crate) fn message_secrets_for_epoch(
733 &self,
734 epoch: GroupEpoch,
735 ) -> Result<&MessageSecrets, SecretTreeError> {
736 if epoch < self.context().epoch() {
737 self.message_secrets_store
738 .secrets_for_epoch(epoch)
739 .ok_or(SecretTreeError::TooDistantInThePast)
740 } else {
741 Ok(self.message_secrets_store.message_secrets())
742 }
743 }
744
745 pub(crate) fn message_secrets_and_leaves(
751 &self,
752 epoch: GroupEpoch,
753 ) -> Result<(&MessageSecrets, &[Member]), SecretTreeError> {
754 if epoch < self.context().epoch() {
755 self.message_secrets_store
756 .secrets_and_leaves_for_epoch(epoch)
757 .ok_or(SecretTreeError::TooDistantInThePast)
758 } else {
759 Ok((self.message_secrets_store.message_secrets(), &[]))
762 }
763 }
764
765 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
767 &self,
768 framing_parameters: FramingParameters,
769 extensions: Extensions<GroupContext>,
770 signer: &impl Signer,
771 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
772 {
773 let required_extension = extensions
775 .iter()
776 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
777 if let Some(required_extension) = required_extension {
778 let required_capabilities = required_extension.as_required_capabilities_extension()?;
779 self.own_leaf_node()
781 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
782 .capabilities()
783 .supports_required_capabilities(required_capabilities)?;
784
785 self.public_group()
788 .check_extension_support(required_capabilities.extension_types())?;
789 }
790 let proposal = GroupContextExtensionProposal::new(extensions);
791 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
792 AuthenticatedContent::member_proposal(
793 framing_parameters,
794 self.own_leaf_index(),
795 proposal,
796 self.context(),
797 signer,
798 )
799 .map_err(|e| e.into())
800 }
801
802 #[cfg(feature = "virtual-clients-draft")]
810 pub(crate) fn vc_derivation_state_at_epoch<Storage: StorageProvider>(
811 &self,
812 storage: &Storage,
813 epoch: GroupEpoch,
814 ) -> Result<
815 Option<crate::components::vc_derivation_info::VcDerivationEpochState>,
816 VcDerivationStateError<Storage::Error>,
817 > {
818 let binding: Option<crate::components::vc_derivation_info::VcEmulationBinding> = storage
819 .vc_emulation_binding(self.group_id(), &epoch)
820 .map_err(VcDerivationStateError::Storage)?;
821 let Some(epoch_id) = binding.map(|binding| binding.into_epoch_id()) else {
822 return Ok(None);
823 };
824 let state = storage
825 .vc_derivation_epoch_state(&epoch_id)
826 .map_err(VcDerivationStateError::Storage)?
827 .ok_or_else(|| {
828 log::error!("vc: group is bound to derivation epoch, but state is missing");
829 VcDerivationStateError::MissingDerivationEpochState
830 })?;
831 Ok(Some(state))
832 }
833
834 #[cfg(feature = "virtual-clients-draft")]
840 pub fn vc_derivation_epoch_at<Storage: StorageProvider>(
841 &self,
842 storage: &Storage,
843 epoch: GroupEpoch,
844 ) -> Result<Option<crate::components::vc_derivation_info::EpochId>, Storage::Error> {
845 let binding: Option<crate::components::vc_derivation_info::VcEmulationBinding> =
846 storage.vc_emulation_binding(self.group_id(), &epoch)?;
847 Ok(binding.map(|binding| binding.into_epoch_id()))
848 }
849
850 #[cfg(feature = "virtual-clients-draft")]
858 pub fn is_emulation_group(&self) -> bool {
859 self.emulation_group
860 }
861
862 #[cfg(feature = "virtual-clients-draft")]
877 pub fn newest_vc_derivation_epoch<Storage: StorageProvider>(
878 &self,
879 storage: &Storage,
880 ) -> Result<Option<crate::components::vc_derivation_info::EpochId>, Storage::Error> {
881 crate::components::vc_derivation_info::newest_vc_derivation_epoch(storage, self.group_id())
882 }
883
884 #[cfg(feature = "virtual-clients-draft")]
891 pub fn delete_vc_derivation_epochs<Provider: OpenMlsProvider>(
892 &self,
893 provider: &Provider,
894 deletion: VcDerivationEpochDeletion,
895 ) -> Result<VcDerivationEpochDeletionResult, Provider::StorageError> {
896 use crate::components::vc_derivation_info::VcDerivationEpochLog;
897
898 let storage = provider.storage();
899 let mut log = VcDerivationEpochLog::load(storage, self.group_id())?;
900 if log.is_empty() {
901 return Ok(VcDerivationEpochDeletionResult::default());
902 }
903 let cutoff = match deletion.time {
904 VcDerivationEpochDeletionTime::BeforeTimestamp(timestamp) => timestamp,
905 VcDerivationEpochDeletionTime::OlderThanDuration(duration) => SystemTime::now()
909 .checked_sub(duration)
910 .unwrap_or(SystemTime::UNIX_EPOCH),
911 };
912 let mut dropped = log.drop_superseded_before(cutoff);
913 if let Some(max_epochs) = deletion.max_epochs {
914 dropped.extend(log.shrink_to(max_epochs));
915 }
916 self.release_vc_derivation_epochs(storage, dropped)
917 }
918
919 #[cfg(feature = "virtual-clients-draft")]
922 fn apply_vc_derivation_epoch_retention<Storage: StorageProvider>(
923 &self,
924 storage: &Storage,
925 ) -> Result<(), Storage::Error> {
926 use crate::components::vc_derivation_info::VcDerivationEpochLog;
927
928 let mut log = VcDerivationEpochLog::load(storage, self.group_id())?;
929 let max_epochs = self
930 .mls_group_config
931 .vc_derivation_epoch_retention_policy()
932 .max_epochs()
933 .unwrap_or(usize::MAX);
934 let dropped = log.shrink_to(max_epochs);
935 if dropped.is_empty() {
936 return Ok(());
937 }
938 self.release_vc_derivation_epochs(storage, dropped)?;
939 Ok(())
940 }
941
942 #[cfg(feature = "virtual-clients-draft")]
946 fn release_vc_derivation_epochs<Storage: StorageProvider>(
947 &self,
948 storage: &Storage,
949 dropped: Vec<crate::components::vc_derivation_info::EpochId>,
950 ) -> Result<VcDerivationEpochDeletionResult, Storage::Error> {
951 use crate::components::vc_derivation_info::{EpochId, VcDerivationEpochState};
952
953 storage.delete_vc_derivation_epoch_log_entries(self.group_id(), &dropped)?;
954 let swept: Vec<EpochId> = storage.delete_unreferenced_vc_derivation_epoch_states()?;
955 let mut result = VcDerivationEpochDeletionResult::default();
956 for epoch_id in dropped {
957 if swept.contains(&epoch_id) {
958 result.deleted.push(epoch_id);
959 continue;
960 }
961 let state: Option<VcDerivationEpochState> =
964 storage.vc_derivation_epoch_state(&epoch_id)?;
965 if state.is_some() {
966 result.kept.push(epoch_id);
967 }
968 }
969 Ok(result)
970 }
971
972 #[cfg(feature = "virtual-clients-draft")]
976 fn drop_all_vc_derivation_epoch_references<Storage: StorageProvider>(
977 &self,
978 storage: &Storage,
979 ) -> Result<(), Storage::Error> {
980 use crate::components::vc_derivation_info::EpochId;
981
982 storage.delete_all_vc_emulation_bindings(self.group_id())?;
983 storage.delete_vc_derivation_epoch_log(self.group_id())?;
984 storage.delete_unreferenced_vc_derivation_epoch_states::<EpochId>()?;
985 Ok(())
986 }
987
988 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
990 &mut self,
991 public_message: AuthenticatedContent,
992 provider: &Provider,
993 ) -> Result<EncryptionOutput, MessageEncryptionError<Provider::StorageError>> {
994 let padding_size = self.configuration().padding_size();
995
996 #[cfg(feature = "virtual-clients-draft")]
1000 let derivation_state = self
1001 .vc_derivation_state_at_epoch(provider.storage(), self.epoch())
1002 .map_err(|e| match e {
1003 VcDerivationStateError::Storage(e) => MessageEncryptionError::StorageError(e),
1004 VcDerivationStateError::MissingDerivationEpochState => {
1005 MessageEncryptionError::VirtualClientsError(
1006 crate::components::vc_derivation_info::VirtualClientsError::MissingDerivationEpochState,
1007 )
1008 }
1009 })?;
1010 #[cfg(feature = "virtual-clients-draft")]
1011 let emulator_ctx: Option<crate::framing::EmulatorReuseGuardCtx<'_>> = derivation_state
1012 .as_ref()
1013 .map(|state| state.reuse_guard_inputs());
1014
1015 let msg = PrivateMessage::try_from_authenticated_content(
1016 provider.crypto(),
1017 provider.rand(),
1018 &public_message,
1019 self.ciphersuite(),
1020 self.message_secrets_store.message_secrets_mut(),
1021 padding_size,
1022 #[cfg(feature = "virtual-clients-draft")]
1023 emulator_ctx.as_ref(),
1024 )?;
1025
1026 #[cfg(feature = "virtual-clients-draft")]
1031 let msg = {
1032 use crate::components::vc_derivation_info::RatchetType;
1033 let mut msg = msg;
1034 if let Some(state) = &derivation_state {
1035 let ratchet_type = match public_message.content().content_type() {
1036 ContentType::Application => RatchetType::Application,
1037 ContentType::Proposal | ContentType::Commit => RatchetType::Handshake,
1038 };
1039 let generation_id = state
1040 .derive_generation_id(
1041 provider.crypto(),
1042 self.group_id(),
1043 self.epoch(),
1044 msg.generation,
1045 ratchet_type,
1046 )
1047 .map_err(MessageEncryptionError::VirtualClientsError)?;
1048 msg.generation_id = Some(generation_id);
1049 }
1050 msg
1051 };
1052
1053 provider
1054 .storage()
1055 .write_message_secrets(self.group_id(), &self.message_secrets_store)
1056 .map_err(MessageEncryptionError::StorageError)?;
1057
1058 Ok(msg)
1059 }
1060
1061 pub(crate) fn outgoing_wire_format(&self) -> WireFormat {
1063 self.mls_group_config.wire_format_policy().outgoing().into()
1064 }
1065
1066 pub(crate) fn outgoing_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
1072 #[cfg(feature = "extensions-draft")]
1073 {
1074 self.assembled_authenticated_data()
1075 }
1076 #[cfg(not(feature = "extensions-draft"))]
1077 {
1078 Ok(self.aad.clone())
1079 }
1080 }
1081
1082 #[cfg(feature = "extensions-draft")]
1087 pub(crate) fn assembled_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
1088 if !self.context().safe_aad_required() {
1089 return Ok(self.aad.clone());
1090 }
1091 crate::framing::safe_aad::assemble_authenticated_data(&self.safe_aad, &self.aad)
1092 .map_err(|_| LibraryError::custom("SafeAad serialization failed"))
1093 }
1094
1095 pub fn delete_past_epoch_secrets<Provider: OpenMlsProvider>(
1099 &mut self,
1100 provider: &Provider,
1101 policy: PastEpochDeletion,
1102 ) -> Result<(), DeletePastEpochSecretsError<Provider::StorageError>> {
1103 self.message_secrets_store.delete_past_epoch_secrets(policy);
1105 provider
1107 .storage()
1108 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
1109
1110 Ok(())
1111 }
1112
1113 pub fn proposal_store(&self) -> &ProposalStore {
1115 self.public_group.proposal_store()
1116 }
1117
1118 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
1120 self.public_group.proposal_store_mut()
1121 }
1122
1123 pub(crate) fn context(&self) -> &GroupContext {
1125 self.public_group.group_context()
1126 }
1127
1128 pub(crate) fn version(&self) -> ProtocolVersion {
1130 self.public_group.version()
1131 }
1132
1133 #[inline]
1135 pub(crate) fn reset_aad(&mut self) {
1136 self.aad.clear();
1137 #[cfg(feature = "extensions-draft")]
1138 {
1139 self.safe_aad = SafeAad::empty();
1140 }
1141 }
1142
1143 pub fn public_group(&self) -> &PublicGroup {
1145 &self.public_group
1146 }
1147}
1148
1149#[cfg(feature = "virtual-clients-draft")]
1160#[derive(Debug, Clone)]
1161pub struct HandshakeConfirmationData {
1162 pub epoch: GroupEpoch,
1165 pub generation: u32,
1167 pub generation_id: Option<crate::components::vc_derivation_info::GenerationId>,
1172}
1173
1174pub(crate) struct HandshakeFramingOutput {
1182 pub(crate) message: MlsMessageOut,
1183 #[cfg(feature = "virtual-clients-draft")]
1184 pub(crate) confirmation: Option<HandshakeConfirmationData>,
1185}
1186
1187impl MlsGroup {
1189 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
1194 &self,
1195 store: &Storage,
1196 keypair_references: &[EncryptionKeyPair],
1197 ) -> Result<(), Storage::Error> {
1198 store.write_encryption_epoch_key_pairs(
1199 self.group_id(),
1200 &self.context().epoch(),
1201 self.own_leaf_index().u32(),
1202 keypair_references,
1203 )
1204 }
1205
1206 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
1211 &self,
1212 store: &Storage,
1213 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
1214 store.encryption_epoch_key_pairs(
1215 self.group_id(),
1216 &self.context().epoch(),
1217 self.own_leaf_index().u32(),
1218 )
1219 }
1220
1221 #[cfg(not(feature = "virtual-clients-draft"))]
1226 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
1227 &self,
1228 store: &Storage,
1229 ) -> Result<(), Storage::Error> {
1230 store.delete_encryption_epoch_key_pairs(
1231 self.group_id(),
1232 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
1233 self.own_leaf_index().u32(),
1234 )
1235 }
1236
1237 #[cfg(feature = "virtual-clients-draft")]
1238 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
1239 &self,
1240 store: &Storage,
1241 previous_own_leaf_index: LeafNodeIndex,
1242 ) -> Result<(), Storage::Error> {
1243 store.delete_encryption_epoch_key_pairs(
1249 self.group_id(),
1250 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
1251 previous_own_leaf_index.u32(),
1252 )
1253 }
1254
1255 pub(super) fn store<Storage: crate::storage::StorageProvider>(
1258 &self,
1259 storage: &Storage,
1260 ) -> Result<(), Storage::Error> {
1261 self.public_group.store(storage)?;
1262 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
1263 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
1264 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
1265 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
1266 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
1267 storage.write_group_state(self.group_id(), &self.group_state)?;
1268 #[cfg(feature = "extensions-draft")]
1269 if let Some(application_export_tree) = &self.application_export_tree {
1270 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
1271 }
1272
1273 Ok(())
1274 }
1275
1276 fn content_to_mls_message(
1280 &mut self,
1281 mls_auth_content: AuthenticatedContent,
1282 provider: &impl OpenMlsProvider,
1283 ) -> Result<HandshakeFramingOutput, LibraryError> {
1284 let output = match self.configuration().wire_format_policy().outgoing() {
1285 OutgoingWireFormatPolicy::AlwaysPlaintext => {
1286 let mut plaintext: PublicMessage = mls_auth_content.into();
1287 if plaintext.sender().is_member() {
1289 plaintext.set_membership_tag(
1290 provider.crypto(),
1291 self.ciphersuite(),
1292 self.message_secrets().membership_key(),
1293 self.message_secrets().serialized_context(),
1294 )?;
1295 }
1296 HandshakeFramingOutput {
1297 message: plaintext.into(),
1298 #[cfg(feature = "virtual-clients-draft")]
1299 confirmation: None,
1300 }
1301 }
1302 OutgoingWireFormatPolicy::AlwaysCiphertext => {
1303 #[cfg(feature = "virtual-clients-draft")]
1307 let epoch = self.epoch();
1308 let encryption_output = self
1309 .encrypt(mls_auth_content, provider)
1310 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
1312 let message = MlsMessageOut::from_private_message(
1313 encryption_output.private_message,
1314 self.version(),
1315 );
1316 HandshakeFramingOutput {
1317 message,
1318 #[cfg(feature = "virtual-clients-draft")]
1319 confirmation: Some(HandshakeConfirmationData {
1320 epoch,
1321 generation: encryption_output.generation,
1322 generation_id: encryption_output.generation_id,
1323 }),
1324 }
1325 }
1326 };
1327 Ok(output)
1328 }
1329
1330 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
1333 match self.group_state {
1334 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
1335 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
1336 MlsGroupState::Operational => Ok(()),
1337 }
1338 }
1339}
1340
1341impl MlsGroup {
1343 #[cfg(any(feature = "test-utils", test))]
1344 pub fn export_group_context(&self) -> &GroupContext {
1345 self.context()
1346 }
1347
1348 #[cfg(any(feature = "test-utils", test))]
1349 pub fn tree_hash(&self) -> &[u8] {
1350 self.public_group().group_context().tree_hash()
1351 }
1352
1353 #[cfg(any(feature = "test-utils", test))]
1354 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
1355 self.message_secrets_store.message_secrets_mut()
1356 }
1357
1358 #[cfg(any(feature = "test-utils", test))]
1359 pub fn print_ratchet_tree(&self, message: &str) {
1360 println!("{}: {}", message, self.public_group().export_ratchet_tree());
1361 }
1362
1363 #[cfg(any(feature = "test-utils", test))]
1364 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
1365 self.public_group.context_mut()
1366 }
1367
1368 #[cfg(test)]
1369 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
1370 self.own_leaf_index = own_leaf_index;
1371 }
1372
1373 #[cfg(test)]
1374 pub(crate) fn own_tree_position(&self) -> TreePosition {
1375 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
1376 }
1377
1378 #[cfg(test)]
1379 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
1380 &self.message_secrets_store
1381 }
1382
1383 #[cfg(test)]
1384 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
1385 &self.resumption_psk_store
1386 }
1387
1388 #[cfg(test)]
1389 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
1390 self.public_group.set_group_context(group_context)
1391 }
1392
1393 #[cfg(any(test, feature = "test-utils"))]
1394 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
1395 let loaded = MlsGroup::load(storage, self.group_id())
1396 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
1397 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
1398
1399 if self != &other {
1400 let mut diagnostics = Vec::new();
1401
1402 if self.mls_group_config != other.mls_group_config {
1403 diagnostics.push(format!(
1404 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
1405 self.mls_group_config, other.mls_group_config
1406 ));
1407 }
1408 if self.public_group != other.public_group {
1409 diagnostics.push(format!(
1410 "public_group:\n Current: {:?}\n Loaded: {:?}",
1411 self.public_group, other.public_group
1412 ));
1413 }
1414 if self.group_epoch_secrets != other.group_epoch_secrets {
1415 diagnostics.push(format!(
1416 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
1417 self.group_epoch_secrets, other.group_epoch_secrets
1418 ));
1419 }
1420 if self.own_leaf_index != other.own_leaf_index {
1421 diagnostics.push(format!(
1422 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
1423 self.own_leaf_index, other.own_leaf_index
1424 ));
1425 }
1426 if self.message_secrets_store != other.message_secrets_store {
1427 diagnostics.push(format!(
1428 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
1429 self.message_secrets_store, other.message_secrets_store
1430 ));
1431 }
1432 if self.resumption_psk_store != other.resumption_psk_store {
1433 diagnostics.push(format!(
1434 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
1435 self.resumption_psk_store, other.resumption_psk_store
1436 ));
1437 }
1438 if self.own_leaf_nodes != other.own_leaf_nodes {
1439 diagnostics.push(format!(
1440 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
1441 self.own_leaf_nodes, other.own_leaf_nodes
1442 ));
1443 }
1444 if self.aad != other.aad {
1445 diagnostics.push(format!(
1446 "aad:\n Current: {:?}\n Loaded: {:?}",
1447 self.aad, other.aad
1448 ));
1449 }
1450 if self.group_state != other.group_state {
1451 diagnostics.push(format!(
1452 "group_state:\n Current: {:?}\n Loaded: {:?}",
1453 self.group_state, other.group_state
1454 ));
1455 }
1456 #[cfg(feature = "extensions-draft")]
1457 if self.application_export_tree != other.application_export_tree {
1458 diagnostics.push(format!(
1459 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
1460 self.application_export_tree, other.application_export_tree
1461 ));
1462 }
1463 #[cfg(feature = "virtual-clients-draft")]
1464 if self.emulation_group != other.emulation_group {
1465 diagnostics.push(format!(
1466 "emulation_group:\n Current: {:?}\n Loaded: {:?}",
1467 self.emulation_group, other.emulation_group
1468 ));
1469 }
1470
1471 log::error!(
1472 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
1473 diagnostics.len(),
1474 diagnostics.join("\n\n")
1475 );
1476
1477 return Err(LibraryError::custom(
1478 "Loaded group does not match current group",
1479 ));
1480 }
1481
1482 Ok(())
1483 }
1484}
1485
1486#[derive(Debug)]
1489pub struct StagedWelcome {
1490 mls_group_config: MlsGroupJoinConfig,
1492 public_group: PublicGroup,
1493 group_epoch_secrets: GroupEpochSecrets,
1494 own_leaf_index: LeafNodeIndex,
1495
1496 message_secrets_store: MessageSecretsStore,
1503
1504 #[cfg(feature = "extensions-draft")]
1507 application_export_secret: ApplicationExportSecret,
1508
1509 resumption_psk_store: ResumptionPskStore,
1511
1512 verifiable_group_info: VerifiableGroupInfo,
1514
1515 key_material: WelcomeKeyMaterial,
1517
1518 path_keypairs: Option<Vec<EncryptionKeyPair>>,
1520
1521 #[cfg(feature = "virtual-clients-draft")]
1524 emulation_group: bool,
1525}
1526
1527pub struct ProcessedWelcome {
1534 mls_group_config: MlsGroupJoinConfig,
1536
1537 ciphersuite: Ciphersuite,
1540 group_secrets: GroupSecrets,
1541 epoch_secrets: crate::schedule::EpochSecretsResult,
1542 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
1543 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
1544 key_material: WelcomeKeyMaterial,
1545}
1546
1547#[derive(Debug)]
1549pub struct WelcomeKeyMaterial {
1550 inner: WelcomeKeyMaterialInner,
1551}
1552
1553#[derive(Debug)]
1562pub(crate) enum WelcomeKeyMaterialInner {
1563 KeyPackage(Box<KeyPackageBundle>),
1566 #[cfg(feature = "virtual-clients-draft")]
1569 VirtualClient(crate::components::vc_derivation_info::VcWelcomeMaterial),
1570}
1571
1572impl WelcomeKeyMaterial {
1573 pub(crate) fn with_key_package_bundle(key_package: KeyPackageBundle) -> Self {
1575 Self {
1576 inner: WelcomeKeyMaterialInner::KeyPackage(Box::new(key_package)),
1577 }
1578 }
1579
1580 #[cfg(feature = "virtual-clients-draft")]
1584 pub(crate) fn with_vc_welcome_material(
1585 material: crate::components::vc_derivation_info::VcWelcomeMaterial,
1586 ) -> Self {
1587 Self {
1588 inner: WelcomeKeyMaterialInner::VirtualClient(material),
1589 }
1590 }
1591
1592 pub(crate) fn inner(&self) -> &WelcomeKeyMaterialInner {
1593 &self.inner
1594 }
1595
1596 pub fn key_package_ref(
1602 &self,
1603 crypto: &impl OpenMlsCrypto,
1604 ) -> Result<crate::ciphersuite::hash_ref::KeyPackageRef, LibraryError> {
1605 match &self.inner {
1606 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.key_package().hash_ref(crypto),
1607 #[cfg(feature = "virtual-clients-draft")]
1608 WelcomeKeyMaterialInner::VirtualClient(material) => {
1609 Ok(material.key_package_ref.clone())
1610 }
1611 }
1612 }
1613
1614 pub fn init_private_key(&self) -> &crate::ciphersuite::HpkePrivateKey {
1616 match &self.inner {
1617 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.init_private_key(),
1618 #[cfg(feature = "virtual-clients-draft")]
1619 WelcomeKeyMaterialInner::VirtualClient(material) => &material.init_private_key,
1620 }
1621 }
1622
1623 pub fn hpke_init_key(&self) -> &InitKey {
1625 match &self.inner {
1626 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.key_package().hpke_init_key(),
1627 #[cfg(feature = "virtual-clients-draft")]
1628 WelcomeKeyMaterialInner::VirtualClient(material) => &material.init_key,
1629 }
1630 }
1631
1632 pub fn key_package_bundle(&self) -> Option<&KeyPackageBundle> {
1636 match &self.inner {
1637 WelcomeKeyMaterialInner::KeyPackage(bundle) => Some(bundle),
1638 #[cfg(feature = "virtual-clients-draft")]
1639 WelcomeKeyMaterialInner::VirtualClient(_) => None,
1640 }
1641 }
1642
1643 #[cfg(feature = "virtual-clients-draft")]
1646 pub(crate) fn vc_welcome_material(
1647 &self,
1648 ) -> Option<&crate::components::vc_derivation_info::VcWelcomeMaterial> {
1649 match &self.inner {
1650 WelcomeKeyMaterialInner::KeyPackage(_) => None,
1651 WelcomeKeyMaterialInner::VirtualClient(material) => Some(material),
1652 }
1653 }
1654
1655 fn encryption_key_pair(&self) -> EncryptionKeyPair {
1657 match &self.inner {
1658 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.encryption_key_pair(),
1659 #[cfg(feature = "virtual-clients-draft")]
1660 WelcomeKeyMaterialInner::VirtualClient(material) => material.encryption_keypair.clone(),
1661 }
1662 }
1663}