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;
82pub use reinit::ReInitInfo;
83
84use config::*;
85
86pub(crate) mod branch;
88pub(crate) mod builder;
89pub(crate) mod commit_builder;
90pub(crate) mod config;
91pub(crate) mod creation;
92pub(crate) mod errors;
93pub(crate) mod membership;
94pub(crate) mod past_secrets;
95pub(crate) mod processing;
96pub(crate) mod proposal;
97pub(crate) mod proposal_store;
98pub(crate) mod reinit;
99pub(crate) mod staged_commit;
100
101#[cfg(feature = "extensions-draft")]
102pub(crate) mod app_ephemeral;
103
104#[cfg(feature = "targeted-messages-draft")]
105mod targeted_messages;
106
107#[cfg(feature = "virtual-clients-draft")]
108mod vc_application_secret;
109
110#[cfg(test)]
112pub(crate) mod tests_and_kats;
113
114#[derive(Debug)]
115pub(crate) struct CreateCommitResult {
116 pub(crate) commit: AuthenticatedContent,
117 pub(crate) welcome_option: Option<Welcome>,
118 pub(crate) staged_commit: StagedCommit,
119 pub(crate) group_info: Option<GroupInfo>,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct Member {
125 pub index: LeafNodeIndex,
127 pub credential: Credential,
129 pub encryption_key: Vec<u8>,
131 pub signature_key: Vec<u8>,
133}
134
135impl Member {
136 pub fn new(
138 index: LeafNodeIndex,
139 encryption_key: Vec<u8>,
140 signature_key: Vec<u8>,
141 credential: Credential,
142 ) -> Self {
143 Self {
144 index,
145 encryption_key,
146 signature_key,
147 credential,
148 }
149 }
150}
151
152#[derive(Debug, Serialize, Deserialize)]
155#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
156pub enum PendingCommitState {
157 Member(StagedCommit),
159 External(StagedCommit),
161}
162
163impl PendingCommitState {
164 pub(crate) fn staged_commit(&self) -> &StagedCommit {
167 match self {
168 PendingCommitState::Member(pc) => pc,
169 PendingCommitState::External(pc) => pc,
170 }
171 }
172}
173
174impl From<PendingCommitState> for StagedCommit {
175 fn from(pcs: PendingCommitState) -> Self {
176 match pcs {
177 PendingCommitState::Member(pc) => pc,
178 PendingCommitState::External(pc) => pc,
179 }
180 }
181}
182
183#[derive(Debug, Serialize, Deserialize)]
228#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
229pub enum MlsGroupState {
230 PendingCommit(Box<PendingCommitState>),
232 Operational,
234 Inactive,
236}
237
238#[derive(Debug)]
263#[cfg_attr(feature = "migration-import", derive(serde::Deserialize))]
264#[cfg_attr(
265 all(feature = "migration-import", feature = "test-utils"),
266 derive(serde::Serialize)
267)]
268#[cfg_attr(feature = "test-utils", derive(Clone, PartialEq))]
269pub struct MlsGroup {
270 mls_group_config: MlsGroupJoinConfig,
272 public_group: PublicGroup,
274 group_epoch_secrets: GroupEpochSecrets,
276 own_leaf_index: LeafNodeIndex,
278 message_secrets_store: MessageSecretsStore,
285 resumption_psk_store: ResumptionPskStore,
287 own_leaf_nodes: Vec<LeafNode>,
291 aad: Vec<u8>,
295 #[cfg(feature = "extensions-draft")]
299 #[cfg_attr(
303 feature = "migration-import",
304 serde(default = "crate::framing::SafeAad::empty")
305 )]
306 safe_aad: SafeAad,
307 group_state: MlsGroupState,
310 #[cfg(feature = "extensions-draft")]
314 #[cfg_attr(feature = "migration-import", serde(default))]
318 application_export_tree: Option<ApplicationExportTree>,
319 #[cfg(feature = "virtual-clients-draft")]
323 #[cfg_attr(feature = "migration-import", serde(default))]
326 emulation_group: bool,
327}
328
329impl MlsGroup {
330 pub fn configuration(&self) -> &MlsGroupJoinConfig {
334 &self.mls_group_config
335 }
336
337 pub fn set_configuration<Storage: StorageProvider>(
339 &mut self,
340 storage: &Storage,
341 mls_group_config: &MlsGroupJoinConfig,
342 ) -> Result<(), Storage::Error> {
343 let policy_changed = self.mls_group_config.past_epoch_deletion_policy()
344 != mls_group_config.past_epoch_deletion_policy();
345 #[cfg(feature = "virtual-clients-draft")]
346 let retention_changed = self.mls_group_config.vc_derivation_epoch_retention_policy()
347 != mls_group_config.vc_derivation_epoch_retention_policy();
348
349 self.mls_group_config = mls_group_config.clone();
350 storage.write_mls_join_config(self.group_id(), mls_group_config)?;
351
352 if policy_changed {
353 self.resize_message_secrets_store(mls_group_config.past_epoch_deletion_policy());
355 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
356 }
357
358 #[cfg(feature = "virtual-clients-draft")]
359 if retention_changed {
360 self.apply_vc_derivation_epoch_retention(storage)?;
361 }
362
363 Ok(())
364 }
365
366 pub fn set_aad(&mut self, aad: Vec<u8>) {
370 self.aad = aad;
371 }
372
373 pub fn aad(&self) -> &[u8] {
376 &self.aad
377 }
378
379 #[cfg(feature = "extensions-draft")]
389 pub fn set_safe_aad(&mut self, items: Vec<SafeAadItem>) -> Result<(), SafeAadError> {
390 self.safe_aad = SafeAad::from_items(items)?;
391 Ok(())
392 }
393
394 #[cfg(feature = "extensions-draft")]
397 pub fn safe_aad_items(&self) -> &[SafeAadItem] {
398 self.safe_aad.items()
399 }
400
401 pub fn ciphersuite(&self) -> Ciphersuite {
405 self.public_group.ciphersuite()
406 }
407
408 pub fn confirmation_tag(&self) -> &ConfirmationTag {
410 self.public_group.confirmation_tag()
411 }
412
413 pub fn is_active(&self) -> bool {
416 !matches!(self.group_state, MlsGroupState::Inactive)
417 }
418
419 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
422 if !self.is_active() {
423 return Err(MlsGroupStateError::UseAfterEviction);
424 }
425 self.public_group
426 .leaf(self.own_leaf_index())
427 .map(|node| node.credential())
428 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
429 }
430
431 pub fn own_leaf_index(&self) -> LeafNodeIndex {
433 self.own_leaf_index
434 }
435
436 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
438 self.public_group().leaf(self.own_leaf_index())
439 }
440
441 pub fn group_id(&self) -> &GroupId {
443 self.public_group.group_id()
444 }
445
446 pub fn epoch(&self) -> GroupEpoch {
448 self.public_group.group_context().epoch()
449 }
450
451 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
453 self.proposal_store().proposals()
454 }
455
456 pub fn treesync(&self) -> &TreeSync {
458 self.public_group.treesync()
459 }
460
461 pub fn pending_commit(&self) -> Option<&StagedCommit> {
465 match self.group_state {
466 MlsGroupState::PendingCommit(ref pending_commit_state) => {
467 Some(pending_commit_state.staged_commit())
468 }
469 MlsGroupState::Operational => None,
470 MlsGroupState::Inactive => None,
471 }
472 }
473
474 pub fn clear_pending_commit<Storage: StorageProvider>(
486 &mut self,
487 storage: &Storage,
488 ) -> Result<(), Storage::Error> {
489 match self.group_state {
490 MlsGroupState::PendingCommit(ref pending_commit_state) => {
491 if let PendingCommitState::Member(_) = **pending_commit_state {
492 self.group_state = MlsGroupState::Operational;
493 storage.write_group_state(self.group_id(), &self.group_state)
494 } else {
495 Ok(())
496 }
497 }
498 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
499 }
500 }
501
502 pub fn clear_pending_proposals<Storage: StorageProvider>(
509 &mut self,
510 storage: &Storage,
511 ) -> Result<(), Storage::Error> {
512 if !self.proposal_store().is_empty() {
514 self.proposal_store_mut().empty();
516
517 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
519 }
520
521 Ok(())
522 }
523
524 pub fn extensions(&self) -> &Extensions<GroupContext> {
526 self.public_group().group_context().extensions()
527 }
528
529 pub fn ext_commit_sender_index(
531 &self,
532 commit: &StagedCommit,
533 ) -> Result<LeafNodeIndex, LibraryError> {
534 self.public_group().ext_commit_sender_index(commit)
535 }
536
537 pub fn load<Storage: crate::storage::StorageProvider>(
541 storage: &Storage,
542 group_id: &GroupId,
543 ) -> Result<Option<MlsGroup>, Storage::Error> {
544 let public_group = PublicGroup::load(storage, group_id)?;
545 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
546 let own_leaf_index = storage.own_leaf_index(group_id)?;
547 let message_secrets_store = storage.message_secrets(group_id)?;
548 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
549 let mls_group_config = storage.mls_group_join_config(group_id)?;
550 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
551 let group_state = storage.group_state(group_id)?;
552 #[cfg(feature = "extensions-draft")]
553 let application_export_tree = storage.application_export_tree(group_id)?;
554 #[cfg(feature = "virtual-clients-draft")]
558 let emulation_group =
559 crate::components::vc_derivation_info::newest_vc_derivation_epoch(storage, group_id)?
560 .is_some();
561
562 let build = || -> Option<Self> {
563 Some(Self {
564 public_group: public_group?,
565 group_epoch_secrets: group_epoch_secrets?,
566 own_leaf_index: own_leaf_index?,
567 message_secrets_store: message_secrets_store?,
568 resumption_psk_store: resumption_psk_store?,
569 mls_group_config: mls_group_config?,
570 own_leaf_nodes,
571 aad: vec![],
572 #[cfg(feature = "extensions-draft")]
573 safe_aad: SafeAad::empty(),
574 group_state: group_state?,
575 #[cfg(feature = "extensions-draft")]
576 application_export_tree,
577 #[cfg(feature = "virtual-clients-draft")]
578 emulation_group,
579 })
580 };
581
582 Ok(build())
583 }
584
585 pub fn delete<Storage: crate::storage::StorageProvider>(
589 &mut self,
590 storage: &Storage,
591 ) -> Result<(), Storage::Error> {
592 PublicGroup::delete(storage, self.group_id())?;
593 storage.delete_own_leaf_index(self.group_id())?;
594 storage.delete_group_epoch_secrets(self.group_id())?;
595 storage.delete_message_secrets(self.group_id())?;
596 storage.delete_all_resumption_psk_secrets(self.group_id())?;
597 storage.delete_group_config(self.group_id())?;
598 storage.delete_own_leaf_nodes(self.group_id())?;
599 storage.delete_group_state(self.group_id())?;
600 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
601
602 #[cfg(feature = "extensions-draft")]
603 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
604
605 #[cfg(feature = "virtual-clients-draft")]
608 self.drop_all_vc_derivation_epoch_references(storage)?;
609
610 self.proposal_store_mut().empty();
611 storage.delete_encryption_epoch_key_pairs(
612 self.group_id(),
613 &self.epoch(),
614 self.own_leaf_index().u32(),
615 )?;
616
617 Ok(())
618 }
619
620 pub fn export_ratchet_tree(&self) -> RatchetTree {
624 self.public_group().export_ratchet_tree()
625 }
626}
627
628#[cfg(feature = "virtual-clients-draft")]
634#[derive(thiserror::Error, Debug, PartialEq, Clone)]
635pub(crate) enum VcDerivationStateError<StorageError> {
636 #[error("Error reading the binding or derivation-epoch state from storage: {0}")]
638 Storage(StorageError),
639 #[error("The group is bound to a derivation epoch, but its state is missing.")]
641 MissingDerivationEpochState,
642}
643
644impl MlsGroup {
646 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
648 self.public_group.required_capabilities()
649 }
650
651 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
653 &self.group_epoch_secrets
654 }
655
656 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
658 self.message_secrets_store.message_secrets()
659 }
660
661 pub(crate) fn resize_message_secrets_store(&mut self, policy: &PastEpochDeletionPolicy) {
665 self.message_secrets_store.resize(policy);
666 }
667
668 pub fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
670 self.mls_group_config.past_epoch_deletion_policy()
671 }
672
673 pub fn set_past_epoch_deletion_policy<Provider: OpenMlsProvider>(
675 &mut self,
676 provider: &Provider,
677 policy: PastEpochDeletionPolicy,
678 ) -> Result<(), SetPastEpochDeletionPolicyError<Provider::StorageError>> {
679 self.resize_message_secrets_store(&policy);
681
682 self.mls_group_config.past_epoch_deletion_policy = policy;
684
685 provider
687 .storage()
688 .write_mls_join_config(self.group_id(), &self.mls_group_config)?;
689
690 provider
692 .storage()
693 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
694
695 Ok(())
696 }
697
698 #[cfg(feature = "virtual-clients-draft")]
700 pub fn vc_derivation_epoch_retention_policy(&self) -> &VcDerivationEpochRetentionPolicy {
701 self.mls_group_config.vc_derivation_epoch_retention_policy()
702 }
703
704 #[cfg(feature = "virtual-clients-draft")]
707 pub fn set_vc_derivation_epoch_retention_policy<Provider: OpenMlsProvider>(
708 &mut self,
709 provider: &Provider,
710 policy: VcDerivationEpochRetentionPolicy,
711 ) -> Result<(), Provider::StorageError> {
712 self.mls_group_config.vc_derivation_epoch_retention_policy = policy;
713 provider
714 .storage()
715 .write_mls_join_config(self.group_id(), &self.mls_group_config)?;
716 self.apply_vc_derivation_epoch_retention(provider.storage())
717 }
718
719 pub(crate) fn message_secrets_for_epoch_mut(
721 &mut self,
722 epoch: GroupEpoch,
723 ) -> Result<&mut MessageSecrets, SecretTreeError> {
724 if epoch < self.context().epoch() {
725 self.message_secrets_store
726 .secrets_for_epoch_mut(epoch)
727 .ok_or(SecretTreeError::TooDistantInThePast)
728 } else {
729 Ok(self.message_secrets_store.message_secrets_mut())
730 }
731 }
732
733 pub(crate) fn message_secrets_for_epoch(
735 &self,
736 epoch: GroupEpoch,
737 ) -> Result<&MessageSecrets, SecretTreeError> {
738 if epoch < self.context().epoch() {
739 self.message_secrets_store
740 .secrets_for_epoch(epoch)
741 .ok_or(SecretTreeError::TooDistantInThePast)
742 } else {
743 Ok(self.message_secrets_store.message_secrets())
744 }
745 }
746
747 pub(crate) fn message_secrets_and_leaves(
753 &self,
754 epoch: GroupEpoch,
755 ) -> Result<(&MessageSecrets, &[Member]), SecretTreeError> {
756 if epoch < self.context().epoch() {
757 self.message_secrets_store
758 .secrets_and_leaves_for_epoch(epoch)
759 .ok_or(SecretTreeError::TooDistantInThePast)
760 } else {
761 Ok((self.message_secrets_store.message_secrets(), &[]))
764 }
765 }
766
767 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
769 &self,
770 framing_parameters: FramingParameters,
771 extensions: Extensions<GroupContext>,
772 signer: &impl Signer,
773 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
774 {
775 let required_extension = extensions
777 .iter()
778 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
779 if let Some(required_extension) = required_extension {
780 let required_capabilities = required_extension.as_required_capabilities_extension()?;
781 self.own_leaf_node()
783 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
784 .capabilities()
785 .supports_required_capabilities(required_capabilities)?;
786
787 self.public_group()
790 .check_extension_support(required_capabilities.extension_types())?;
791 }
792 let proposal = GroupContextExtensionProposal::new(extensions);
793 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
794 AuthenticatedContent::member_proposal(
795 framing_parameters,
796 self.own_leaf_index(),
797 proposal,
798 self.context(),
799 signer,
800 )
801 .map_err(|e| e.into())
802 }
803
804 #[cfg(feature = "virtual-clients-draft")]
812 pub(crate) fn vc_derivation_state_at_epoch<Storage: StorageProvider>(
813 &self,
814 storage: &Storage,
815 epoch: GroupEpoch,
816 ) -> Result<
817 Option<crate::components::vc_derivation_info::VcDerivationEpochState>,
818 VcDerivationStateError<Storage::Error>,
819 > {
820 let binding: Option<crate::components::vc_derivation_info::VcEmulationBinding> = storage
821 .vc_emulation_binding(self.group_id(), &epoch)
822 .map_err(VcDerivationStateError::Storage)?;
823 let Some(epoch_id) = binding.map(|binding| binding.into_epoch_id()) else {
824 return Ok(None);
825 };
826 let state = storage
827 .vc_derivation_epoch_state(&epoch_id)
828 .map_err(VcDerivationStateError::Storage)?
829 .ok_or_else(|| {
830 log::error!("vc: group is bound to derivation epoch, but state is missing");
831 VcDerivationStateError::MissingDerivationEpochState
832 })?;
833 Ok(Some(state))
834 }
835
836 #[cfg(feature = "virtual-clients-draft")]
842 pub fn vc_derivation_epoch_at<Storage: StorageProvider>(
843 &self,
844 storage: &Storage,
845 epoch: GroupEpoch,
846 ) -> Result<Option<crate::components::vc_derivation_info::EpochId>, Storage::Error> {
847 let binding: Option<crate::components::vc_derivation_info::VcEmulationBinding> =
848 storage.vc_emulation_binding(self.group_id(), &epoch)?;
849 Ok(binding.map(|binding| binding.into_epoch_id()))
850 }
851
852 #[cfg(feature = "virtual-clients-draft")]
860 pub fn is_emulation_group(&self) -> bool {
861 self.emulation_group
862 }
863
864 #[cfg(feature = "virtual-clients-draft")]
879 pub fn newest_vc_derivation_epoch<Storage: StorageProvider>(
880 &self,
881 storage: &Storage,
882 ) -> Result<Option<crate::components::vc_derivation_info::EpochId>, Storage::Error> {
883 crate::components::vc_derivation_info::newest_vc_derivation_epoch(storage, self.group_id())
884 }
885
886 #[cfg(feature = "virtual-clients-draft")]
893 pub fn delete_vc_derivation_epochs<Provider: OpenMlsProvider>(
894 &self,
895 provider: &Provider,
896 deletion: VcDerivationEpochDeletion,
897 ) -> Result<VcDerivationEpochDeletionResult, Provider::StorageError> {
898 use crate::components::vc_derivation_info::VcDerivationEpochLog;
899
900 let storage = provider.storage();
901 let mut log = VcDerivationEpochLog::load(storage, self.group_id())?;
902 if log.is_empty() {
903 return Ok(VcDerivationEpochDeletionResult::default());
904 }
905 let cutoff = match deletion.time {
906 VcDerivationEpochDeletionTime::BeforeTimestamp(timestamp) => timestamp,
907 VcDerivationEpochDeletionTime::OlderThanDuration(duration) => SystemTime::now()
911 .checked_sub(duration)
912 .unwrap_or(SystemTime::UNIX_EPOCH),
913 };
914 let mut dropped = log.drop_superseded_before(cutoff);
915 if let Some(max_epochs) = deletion.max_epochs {
916 dropped.extend(log.shrink_to(max_epochs));
917 }
918 self.release_vc_derivation_epochs(storage, dropped)
919 }
920
921 #[cfg(feature = "virtual-clients-draft")]
924 fn apply_vc_derivation_epoch_retention<Storage: StorageProvider>(
925 &self,
926 storage: &Storage,
927 ) -> Result<(), Storage::Error> {
928 use crate::components::vc_derivation_info::VcDerivationEpochLog;
929
930 let mut log = VcDerivationEpochLog::load(storage, self.group_id())?;
931 let max_epochs = self
932 .mls_group_config
933 .vc_derivation_epoch_retention_policy()
934 .max_epochs()
935 .unwrap_or(usize::MAX);
936 let dropped = log.shrink_to(max_epochs);
937 if dropped.is_empty() {
938 return Ok(());
939 }
940 self.release_vc_derivation_epochs(storage, dropped)?;
941 Ok(())
942 }
943
944 #[cfg(feature = "virtual-clients-draft")]
948 fn release_vc_derivation_epochs<Storage: StorageProvider>(
949 &self,
950 storage: &Storage,
951 dropped: Vec<crate::components::vc_derivation_info::EpochId>,
952 ) -> Result<VcDerivationEpochDeletionResult, Storage::Error> {
953 use crate::components::vc_derivation_info::{EpochId, VcDerivationEpochState};
954
955 storage.delete_vc_derivation_epoch_log_entries(self.group_id(), &dropped)?;
956 let swept: Vec<EpochId> = storage.delete_unreferenced_vc_derivation_epoch_states()?;
957 let mut result = VcDerivationEpochDeletionResult::default();
958 for epoch_id in dropped {
959 if swept.contains(&epoch_id) {
960 result.deleted.push(epoch_id);
961 continue;
962 }
963 let state: Option<VcDerivationEpochState> =
966 storage.vc_derivation_epoch_state(&epoch_id)?;
967 if state.is_some() {
968 result.kept.push(epoch_id);
969 }
970 }
971 Ok(result)
972 }
973
974 #[cfg(feature = "virtual-clients-draft")]
978 fn drop_all_vc_derivation_epoch_references<Storage: StorageProvider>(
979 &self,
980 storage: &Storage,
981 ) -> Result<(), Storage::Error> {
982 use crate::components::vc_derivation_info::EpochId;
983
984 storage.delete_all_vc_emulation_bindings(self.group_id())?;
985 storage.delete_vc_derivation_epoch_log(self.group_id())?;
986 storage.delete_unreferenced_vc_derivation_epoch_states::<EpochId>()?;
987 Ok(())
988 }
989
990 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
992 &mut self,
993 public_message: AuthenticatedContent,
994 provider: &Provider,
995 ) -> Result<EncryptionOutput, MessageEncryptionError<Provider::StorageError>> {
996 let padding_size = self.configuration().padding_size();
997
998 #[cfg(feature = "virtual-clients-draft")]
1002 let derivation_state = self
1003 .vc_derivation_state_at_epoch(provider.storage(), self.epoch())
1004 .map_err(|e| match e {
1005 VcDerivationStateError::Storage(e) => MessageEncryptionError::StorageError(e),
1006 VcDerivationStateError::MissingDerivationEpochState => {
1007 MessageEncryptionError::VirtualClientsError(
1008 crate::components::vc_derivation_info::VirtualClientsError::MissingDerivationEpochState,
1009 )
1010 }
1011 })?;
1012 #[cfg(feature = "virtual-clients-draft")]
1013 let emulator_ctx: Option<crate::framing::EmulatorReuseGuardCtx<'_>> = derivation_state
1014 .as_ref()
1015 .map(|state| state.reuse_guard_inputs());
1016
1017 let msg = PrivateMessage::try_from_authenticated_content(
1018 provider.crypto(),
1019 provider.rand(),
1020 &public_message,
1021 self.ciphersuite(),
1022 self.message_secrets_store.message_secrets_mut(),
1023 padding_size,
1024 #[cfg(feature = "virtual-clients-draft")]
1025 emulator_ctx.as_ref(),
1026 )?;
1027
1028 #[cfg(feature = "virtual-clients-draft")]
1033 let msg = {
1034 use crate::components::vc_derivation_info::RatchetType;
1035 let mut msg = msg;
1036 if let Some(state) = &derivation_state {
1037 let ratchet_type = match public_message.content().content_type() {
1038 ContentType::Application => RatchetType::Application,
1039 ContentType::Proposal | ContentType::Commit => RatchetType::Handshake,
1040 };
1041 let generation_id = state
1042 .derive_generation_id(
1043 provider.crypto(),
1044 self.group_id(),
1045 self.epoch(),
1046 msg.generation,
1047 ratchet_type,
1048 )
1049 .map_err(MessageEncryptionError::VirtualClientsError)?;
1050 msg.generation_id = Some(generation_id);
1051 }
1052 msg
1053 };
1054
1055 provider
1056 .storage()
1057 .write_message_secrets(self.group_id(), &self.message_secrets_store)
1058 .map_err(MessageEncryptionError::StorageError)?;
1059
1060 Ok(msg)
1061 }
1062
1063 pub(crate) fn outgoing_wire_format(&self) -> WireFormat {
1065 self.mls_group_config.wire_format_policy().outgoing().into()
1066 }
1067
1068 pub(crate) fn outgoing_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
1074 #[cfg(feature = "extensions-draft")]
1075 {
1076 self.assembled_authenticated_data()
1077 }
1078 #[cfg(not(feature = "extensions-draft"))]
1079 {
1080 Ok(self.aad.clone())
1081 }
1082 }
1083
1084 #[cfg(feature = "extensions-draft")]
1089 pub(crate) fn assembled_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
1090 if !self.context().safe_aad_required() {
1091 return Ok(self.aad.clone());
1092 }
1093 crate::framing::safe_aad::assemble_authenticated_data(&self.safe_aad, &self.aad)
1094 .map_err(|_| LibraryError::custom("SafeAad serialization failed"))
1095 }
1096
1097 pub fn delete_past_epoch_secrets<Provider: OpenMlsProvider>(
1101 &mut self,
1102 provider: &Provider,
1103 policy: PastEpochDeletion,
1104 ) -> Result<(), DeletePastEpochSecretsError<Provider::StorageError>> {
1105 self.message_secrets_store.delete_past_epoch_secrets(policy);
1107 provider
1109 .storage()
1110 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
1111
1112 Ok(())
1113 }
1114
1115 pub fn proposal_store(&self) -> &ProposalStore {
1117 self.public_group.proposal_store()
1118 }
1119
1120 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
1122 self.public_group.proposal_store_mut()
1123 }
1124
1125 pub(crate) fn context(&self) -> &GroupContext {
1127 self.public_group.group_context()
1128 }
1129
1130 pub(crate) fn version(&self) -> ProtocolVersion {
1132 self.public_group.version()
1133 }
1134
1135 #[inline]
1137 pub(crate) fn reset_aad(&mut self) {
1138 self.aad.clear();
1139 #[cfg(feature = "extensions-draft")]
1140 {
1141 self.safe_aad = SafeAad::empty();
1142 }
1143 }
1144
1145 pub fn public_group(&self) -> &PublicGroup {
1147 &self.public_group
1148 }
1149}
1150
1151#[cfg(feature = "virtual-clients-draft")]
1162#[derive(Debug, Clone)]
1163pub struct HandshakeConfirmationData {
1164 pub epoch: GroupEpoch,
1167 pub generation: u32,
1169 pub generation_id: Option<crate::components::vc_derivation_info::GenerationId>,
1174}
1175
1176pub(crate) struct HandshakeFramingOutput {
1184 pub(crate) message: MlsMessageOut,
1185 #[cfg(feature = "virtual-clients-draft")]
1186 pub(crate) confirmation: Option<HandshakeConfirmationData>,
1187}
1188
1189impl MlsGroup {
1191 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
1196 &self,
1197 store: &Storage,
1198 keypair_references: &[EncryptionKeyPair],
1199 ) -> Result<(), Storage::Error> {
1200 store.write_encryption_epoch_key_pairs(
1201 self.group_id(),
1202 &self.context().epoch(),
1203 self.own_leaf_index().u32(),
1204 keypair_references,
1205 )
1206 }
1207
1208 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
1213 &self,
1214 store: &Storage,
1215 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
1216 store.encryption_epoch_key_pairs(
1217 self.group_id(),
1218 &self.context().epoch(),
1219 self.own_leaf_index().u32(),
1220 )
1221 }
1222
1223 #[cfg(not(feature = "virtual-clients-draft"))]
1228 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
1229 &self,
1230 store: &Storage,
1231 ) -> Result<(), Storage::Error> {
1232 store.delete_encryption_epoch_key_pairs(
1233 self.group_id(),
1234 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
1235 self.own_leaf_index().u32(),
1236 )
1237 }
1238
1239 #[cfg(feature = "virtual-clients-draft")]
1240 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
1241 &self,
1242 store: &Storage,
1243 previous_own_leaf_index: LeafNodeIndex,
1244 ) -> Result<(), Storage::Error> {
1245 store.delete_encryption_epoch_key_pairs(
1251 self.group_id(),
1252 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
1253 previous_own_leaf_index.u32(),
1254 )
1255 }
1256
1257 pub(super) fn store<Storage: crate::storage::StorageProvider>(
1260 &self,
1261 storage: &Storage,
1262 ) -> Result<(), Storage::Error> {
1263 self.public_group.store(storage)?;
1264 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
1265 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
1266 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
1267 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
1268 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
1269 storage.write_group_state(self.group_id(), &self.group_state)?;
1270 #[cfg(feature = "extensions-draft")]
1271 if let Some(application_export_tree) = &self.application_export_tree {
1272 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
1273 }
1274
1275 Ok(())
1276 }
1277
1278 fn content_to_mls_message(
1282 &mut self,
1283 mls_auth_content: AuthenticatedContent,
1284 provider: &impl OpenMlsProvider,
1285 ) -> Result<HandshakeFramingOutput, LibraryError> {
1286 let output = match self.configuration().wire_format_policy().outgoing() {
1287 OutgoingWireFormatPolicy::AlwaysPlaintext => {
1288 let mut plaintext: PublicMessage = mls_auth_content.into();
1289 if plaintext.sender().is_member() {
1291 plaintext.set_membership_tag(
1292 provider.crypto(),
1293 self.ciphersuite(),
1294 self.message_secrets().membership_key(),
1295 self.message_secrets().serialized_context(),
1296 )?;
1297 }
1298 HandshakeFramingOutput {
1299 message: plaintext.into(),
1300 #[cfg(feature = "virtual-clients-draft")]
1301 confirmation: None,
1302 }
1303 }
1304 OutgoingWireFormatPolicy::AlwaysCiphertext => {
1305 #[cfg(feature = "virtual-clients-draft")]
1309 let epoch = self.epoch();
1310 let encryption_output = self
1311 .encrypt(mls_auth_content, provider)
1312 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
1314 let message = MlsMessageOut::from_private_message(
1315 encryption_output.private_message,
1316 self.version(),
1317 );
1318 HandshakeFramingOutput {
1319 message,
1320 #[cfg(feature = "virtual-clients-draft")]
1321 confirmation: Some(HandshakeConfirmationData {
1322 epoch,
1323 generation: encryption_output.generation,
1324 generation_id: encryption_output.generation_id,
1325 }),
1326 }
1327 }
1328 };
1329 Ok(output)
1330 }
1331
1332 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
1335 match self.group_state {
1336 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
1337 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
1338 MlsGroupState::Operational => Ok(()),
1339 }
1340 }
1341}
1342
1343impl MlsGroup {
1345 #[cfg(any(feature = "test-utils", test))]
1346 pub fn export_group_context(&self) -> &GroupContext {
1347 self.context()
1348 }
1349
1350 #[cfg(any(feature = "test-utils", test))]
1351 pub fn tree_hash(&self) -> &[u8] {
1352 self.public_group().group_context().tree_hash()
1353 }
1354
1355 #[cfg(any(feature = "test-utils", test))]
1356 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
1357 self.message_secrets_store.message_secrets_mut()
1358 }
1359
1360 #[cfg(any(feature = "test-utils", test))]
1361 pub fn print_ratchet_tree(&self, message: &str) {
1362 println!("{}: {}", message, self.public_group().export_ratchet_tree());
1363 }
1364
1365 #[cfg(any(feature = "test-utils", test))]
1366 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
1367 self.public_group.context_mut()
1368 }
1369
1370 #[cfg(test)]
1371 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
1372 self.own_leaf_index = own_leaf_index;
1373 }
1374
1375 #[cfg(test)]
1376 pub(crate) fn own_tree_position(&self) -> TreePosition {
1377 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
1378 }
1379
1380 #[cfg(test)]
1381 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
1382 &self.message_secrets_store
1383 }
1384
1385 #[cfg(test)]
1386 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
1387 &self.resumption_psk_store
1388 }
1389
1390 #[cfg(test)]
1391 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
1392 self.public_group.set_group_context(group_context)
1393 }
1394
1395 #[cfg(any(test, feature = "test-utils"))]
1396 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
1397 let loaded = MlsGroup::load(storage, self.group_id())
1398 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
1399 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
1400
1401 if self != &other {
1402 let mut diagnostics = Vec::new();
1403
1404 if self.mls_group_config != other.mls_group_config {
1405 diagnostics.push(format!(
1406 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
1407 self.mls_group_config, other.mls_group_config
1408 ));
1409 }
1410 if self.public_group != other.public_group {
1411 diagnostics.push(format!(
1412 "public_group:\n Current: {:?}\n Loaded: {:?}",
1413 self.public_group, other.public_group
1414 ));
1415 }
1416 if self.group_epoch_secrets != other.group_epoch_secrets {
1417 diagnostics.push(format!(
1418 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
1419 self.group_epoch_secrets, other.group_epoch_secrets
1420 ));
1421 }
1422 if self.own_leaf_index != other.own_leaf_index {
1423 diagnostics.push(format!(
1424 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
1425 self.own_leaf_index, other.own_leaf_index
1426 ));
1427 }
1428 if self.message_secrets_store != other.message_secrets_store {
1429 diagnostics.push(format!(
1430 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
1431 self.message_secrets_store, other.message_secrets_store
1432 ));
1433 }
1434 if self.resumption_psk_store != other.resumption_psk_store {
1435 diagnostics.push(format!(
1436 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
1437 self.resumption_psk_store, other.resumption_psk_store
1438 ));
1439 }
1440 if self.own_leaf_nodes != other.own_leaf_nodes {
1441 diagnostics.push(format!(
1442 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
1443 self.own_leaf_nodes, other.own_leaf_nodes
1444 ));
1445 }
1446 if self.aad != other.aad {
1447 diagnostics.push(format!(
1448 "aad:\n Current: {:?}\n Loaded: {:?}",
1449 self.aad, other.aad
1450 ));
1451 }
1452 if self.group_state != other.group_state {
1453 diagnostics.push(format!(
1454 "group_state:\n Current: {:?}\n Loaded: {:?}",
1455 self.group_state, other.group_state
1456 ));
1457 }
1458 #[cfg(feature = "extensions-draft")]
1459 if self.application_export_tree != other.application_export_tree {
1460 diagnostics.push(format!(
1461 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
1462 self.application_export_tree, other.application_export_tree
1463 ));
1464 }
1465 #[cfg(feature = "virtual-clients-draft")]
1466 if self.emulation_group != other.emulation_group {
1467 diagnostics.push(format!(
1468 "emulation_group:\n Current: {:?}\n Loaded: {:?}",
1469 self.emulation_group, other.emulation_group
1470 ));
1471 }
1472
1473 log::error!(
1474 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
1475 diagnostics.len(),
1476 diagnostics.join("\n\n")
1477 );
1478
1479 return Err(LibraryError::custom(
1480 "Loaded group does not match current group",
1481 ));
1482 }
1483
1484 Ok(())
1485 }
1486}
1487
1488#[derive(Debug)]
1491pub struct StagedWelcome {
1492 mls_group_config: MlsGroupJoinConfig,
1494 public_group: PublicGroup,
1495 group_epoch_secrets: GroupEpochSecrets,
1496 own_leaf_index: LeafNodeIndex,
1497
1498 message_secrets_store: MessageSecretsStore,
1505
1506 #[cfg(feature = "extensions-draft")]
1509 application_export_secret: ApplicationExportSecret,
1510
1511 resumption_psk_store: ResumptionPskStore,
1513
1514 verifiable_group_info: VerifiableGroupInfo,
1516
1517 key_material: WelcomeKeyMaterial,
1519
1520 path_keypairs: Option<Vec<EncryptionKeyPair>>,
1522
1523 #[cfg(feature = "virtual-clients-draft")]
1526 emulation_group: bool,
1527}
1528
1529pub struct ProcessedWelcome {
1536 mls_group_config: MlsGroupJoinConfig,
1538
1539 ciphersuite: Ciphersuite,
1542 group_secrets: GroupSecrets,
1543 epoch_secrets: crate::schedule::EpochSecretsResult,
1544 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
1545 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
1546 key_material: WelcomeKeyMaterial,
1547}
1548
1549#[derive(Debug)]
1551pub struct WelcomeKeyMaterial {
1552 inner: WelcomeKeyMaterialInner,
1553}
1554
1555#[derive(Debug)]
1564pub(crate) enum WelcomeKeyMaterialInner {
1565 KeyPackage(Box<KeyPackageBundle>),
1568 #[cfg(feature = "virtual-clients-draft")]
1571 VirtualClient(crate::components::vc_derivation_info::VcWelcomeMaterial),
1572}
1573
1574impl WelcomeKeyMaterial {
1575 pub(crate) fn with_key_package_bundle(key_package: KeyPackageBundle) -> Self {
1577 Self {
1578 inner: WelcomeKeyMaterialInner::KeyPackage(Box::new(key_package)),
1579 }
1580 }
1581
1582 #[cfg(feature = "virtual-clients-draft")]
1586 pub(crate) fn with_vc_welcome_material(
1587 material: crate::components::vc_derivation_info::VcWelcomeMaterial,
1588 ) -> Self {
1589 Self {
1590 inner: WelcomeKeyMaterialInner::VirtualClient(material),
1591 }
1592 }
1593
1594 pub(crate) fn inner(&self) -> &WelcomeKeyMaterialInner {
1595 &self.inner
1596 }
1597
1598 pub fn key_package_ref(
1604 &self,
1605 crypto: &impl OpenMlsCrypto,
1606 ) -> Result<crate::ciphersuite::hash_ref::KeyPackageRef, LibraryError> {
1607 match &self.inner {
1608 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.key_package().hash_ref(crypto),
1609 #[cfg(feature = "virtual-clients-draft")]
1610 WelcomeKeyMaterialInner::VirtualClient(material) => {
1611 Ok(material.key_package_ref.clone())
1612 }
1613 }
1614 }
1615
1616 pub fn init_private_key(&self) -> &crate::ciphersuite::HpkePrivateKey {
1618 match &self.inner {
1619 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.init_private_key(),
1620 #[cfg(feature = "virtual-clients-draft")]
1621 WelcomeKeyMaterialInner::VirtualClient(material) => &material.init_private_key,
1622 }
1623 }
1624
1625 pub fn hpke_init_key(&self) -> &InitKey {
1627 match &self.inner {
1628 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.key_package().hpke_init_key(),
1629 #[cfg(feature = "virtual-clients-draft")]
1630 WelcomeKeyMaterialInner::VirtualClient(material) => &material.init_key,
1631 }
1632 }
1633
1634 pub fn key_package_bundle(&self) -> Option<&KeyPackageBundle> {
1638 match &self.inner {
1639 WelcomeKeyMaterialInner::KeyPackage(bundle) => Some(bundle),
1640 #[cfg(feature = "virtual-clients-draft")]
1641 WelcomeKeyMaterialInner::VirtualClient(_) => None,
1642 }
1643 }
1644
1645 #[cfg(feature = "virtual-clients-draft")]
1648 pub(crate) fn vc_welcome_material(
1649 &self,
1650 ) -> Option<&crate::components::vc_derivation_info::VcWelcomeMaterial> {
1651 match &self.inner {
1652 WelcomeKeyMaterialInner::KeyPackage(_) => None,
1653 WelcomeKeyMaterialInner::VirtualClient(material) => Some(material),
1654 }
1655 }
1656
1657 fn encryption_key_pair(&self) -> EncryptionKeyPair {
1659 match &self.inner {
1660 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.encryption_key_pair(),
1661 #[cfg(feature = "virtual-clients-draft")]
1662 WelcomeKeyMaterialInner::VirtualClient(material) => material.encryption_keypair.clone(),
1663 }
1664 }
1665}