1use past_secrets::MessageSecretsStore;
7use proposal_store::ProposalQueue;
8use serde::{Deserialize, Serialize};
9use tls_codec::Serialize as _;
10
11#[cfg(test)]
12use crate::treesync::node::leaf_node::TreePosition;
13
14use super::proposal_store::{ProposalStore, QueuedProposal};
15use crate::{
16 binary_tree::array_representation::LeafNodeIndex,
17 ciphersuite::{hash_ref::ProposalRef, signable::Signable},
18 credentials::Credential,
19 error::LibraryError,
20 extensions::Extensions,
21 framing::{mls_auth_content::AuthenticatedContent, *},
22 group::{
23 CreateGroupContextExtProposalError, DeletePastEpochSecretsError, Extension, ExtensionType,
24 ExternalPubExtension, GroupContext, GroupEpoch, GroupId, MlsGroupJoinConfig,
25 MlsGroupStateError, OutgoingWireFormatPolicy, PublicGroup, RatchetTreeExtension,
26 RequiredCapabilitiesExtension, SetPastEpochDeletionPolicyError, StagedCommit,
27 },
28 key_packages::KeyPackageBundle,
29 messages::{
30 group_info::{GroupInfo, GroupInfoTBS, VerifiableGroupInfo},
31 proposals::*,
32 ConfirmationTag, GroupSecrets, Welcome,
33 },
34 schedule::{
35 message_secrets::MessageSecrets,
36 psk::{load_psks, store::ResumptionPskStore, PskSecret},
37 GroupEpochSecrets, JoinerSecret, KeySchedule,
38 },
39 storage::{OpenMlsProvider, StorageProvider},
40 treesync::{
41 node::{encryption_keys::EncryptionKeyPair, leaf_node::LeafNode},
42 RatchetTree, TreeSync,
43 },
44 versions::ProtocolVersion,
45};
46use openmls_traits::{
47 crypto::OpenMlsCrypto, signatures::Signer, storage::StorageProvider as _, types::Ciphersuite,
48};
49
50#[cfg(feature = "extensions-draft")]
51use crate::schedule::{application_export_tree::ApplicationExportTree, ApplicationExportSecret};
52
53mod application;
55mod exporting;
56mod updates;
57
58#[cfg(feature = "virtual-clients-draft")]
59pub use application::UnconfirmedMessage;
60pub use proposal::Propose;
61
62use config::*;
63
64pub(crate) mod builder;
66pub(crate) mod commit_builder;
67pub(crate) mod config;
68pub(crate) mod creation;
69pub(crate) mod errors;
70pub(crate) mod membership;
71pub(crate) mod past_secrets;
72pub(crate) mod processing;
73pub(crate) mod proposal;
74pub(crate) mod proposal_store;
75pub(crate) mod staged_commit;
76
77#[cfg(feature = "extensions-draft")]
78pub(crate) mod app_ephemeral;
79
80#[cfg(feature = "targeted-messages-draft")]
81mod targeted_messages;
82
83#[cfg(test)]
85pub(crate) mod tests_and_kats;
86
87#[derive(Debug)]
88pub(crate) struct CreateCommitResult {
89 pub(crate) commit: AuthenticatedContent,
90 pub(crate) welcome_option: Option<Welcome>,
91 pub(crate) staged_commit: StagedCommit,
92 pub(crate) group_info: Option<GroupInfo>,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub struct Member {
98 pub index: LeafNodeIndex,
100 pub credential: Credential,
102 pub encryption_key: Vec<u8>,
104 pub signature_key: Vec<u8>,
106}
107
108impl Member {
109 pub fn new(
111 index: LeafNodeIndex,
112 encryption_key: Vec<u8>,
113 signature_key: Vec<u8>,
114 credential: Credential,
115 ) -> Self {
116 Self {
117 index,
118 encryption_key,
119 signature_key,
120 credential,
121 }
122 }
123}
124
125#[derive(Debug, Serialize, Deserialize)]
128#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
129pub enum PendingCommitState {
130 Member(StagedCommit),
132 External(StagedCommit),
134}
135
136impl PendingCommitState {
137 pub(crate) fn staged_commit(&self) -> &StagedCommit {
140 match self {
141 PendingCommitState::Member(pc) => pc,
142 PendingCommitState::External(pc) => pc,
143 }
144 }
145}
146
147impl From<PendingCommitState> for StagedCommit {
148 fn from(pcs: PendingCommitState) -> Self {
149 match pcs {
150 PendingCommitState::Member(pc) => pc,
151 PendingCommitState::External(pc) => pc,
152 }
153 }
154}
155
156#[derive(Debug, Serialize, Deserialize)]
201#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
202pub enum MlsGroupState {
203 PendingCommit(Box<PendingCommitState>),
205 Operational,
207 Inactive,
209}
210
211#[derive(Debug)]
236#[cfg_attr(feature = "test-utils", derive(Clone, PartialEq))]
237pub struct MlsGroup {
238 mls_group_config: MlsGroupJoinConfig,
240 public_group: PublicGroup,
242 group_epoch_secrets: GroupEpochSecrets,
244 own_leaf_index: LeafNodeIndex,
246 message_secrets_store: MessageSecretsStore,
253 resumption_psk_store: ResumptionPskStore,
255 own_leaf_nodes: Vec<LeafNode>,
259 aad: Vec<u8>,
263 #[cfg(feature = "extensions-draft")]
267 safe_aad: SafeAad,
268 group_state: MlsGroupState,
271 #[cfg(feature = "extensions-draft")]
275 application_export_tree: Option<ApplicationExportTree>,
276}
277
278impl MlsGroup {
279 pub fn configuration(&self) -> &MlsGroupJoinConfig {
283 &self.mls_group_config
284 }
285
286 pub fn set_configuration<Storage: StorageProvider>(
288 &mut self,
289 storage: &Storage,
290 mls_group_config: &MlsGroupJoinConfig,
291 ) -> Result<(), Storage::Error> {
292 self.mls_group_config = mls_group_config.clone();
293 storage.write_mls_join_config(self.group_id(), mls_group_config)
294 }
295
296 pub fn set_aad(&mut self, aad: Vec<u8>) {
300 self.aad = aad;
301 }
302
303 pub fn aad(&self) -> &[u8] {
306 &self.aad
307 }
308
309 #[cfg(feature = "extensions-draft")]
319 pub fn set_safe_aad(&mut self, items: Vec<SafeAadItem>) -> Result<(), SafeAadError> {
320 self.safe_aad = SafeAad::from_items(items)?;
321 Ok(())
322 }
323
324 #[cfg(feature = "extensions-draft")]
327 pub fn safe_aad_items(&self) -> &[SafeAadItem] {
328 self.safe_aad.items()
329 }
330
331 pub fn ciphersuite(&self) -> Ciphersuite {
335 self.public_group.ciphersuite()
336 }
337
338 pub fn confirmation_tag(&self) -> &ConfirmationTag {
340 self.public_group.confirmation_tag()
341 }
342
343 pub fn is_active(&self) -> bool {
346 !matches!(self.group_state, MlsGroupState::Inactive)
347 }
348
349 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
352 if !self.is_active() {
353 return Err(MlsGroupStateError::UseAfterEviction);
354 }
355 self.public_group
356 .leaf(self.own_leaf_index())
357 .map(|node| node.credential())
358 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
359 }
360
361 pub fn own_leaf_index(&self) -> LeafNodeIndex {
363 self.own_leaf_index
364 }
365
366 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
368 self.public_group().leaf(self.own_leaf_index())
369 }
370
371 pub fn group_id(&self) -> &GroupId {
373 self.public_group.group_id()
374 }
375
376 pub fn epoch(&self) -> GroupEpoch {
378 self.public_group.group_context().epoch()
379 }
380
381 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
383 self.proposal_store().proposals()
384 }
385
386 pub fn treesync(&self) -> &TreeSync {
388 self.public_group.treesync()
389 }
390
391 pub fn pending_commit(&self) -> Option<&StagedCommit> {
395 match self.group_state {
396 MlsGroupState::PendingCommit(ref pending_commit_state) => {
397 Some(pending_commit_state.staged_commit())
398 }
399 MlsGroupState::Operational => None,
400 MlsGroupState::Inactive => None,
401 }
402 }
403
404 pub fn clear_pending_commit<Storage: StorageProvider>(
416 &mut self,
417 storage: &Storage,
418 ) -> Result<(), Storage::Error> {
419 match self.group_state {
420 MlsGroupState::PendingCommit(ref pending_commit_state) => {
421 if let PendingCommitState::Member(_) = **pending_commit_state {
422 self.group_state = MlsGroupState::Operational;
423 storage.write_group_state(self.group_id(), &self.group_state)
424 } else {
425 Ok(())
426 }
427 }
428 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
429 }
430 }
431
432 pub fn clear_pending_proposals<Storage: StorageProvider>(
439 &mut self,
440 storage: &Storage,
441 ) -> Result<(), Storage::Error> {
442 if !self.proposal_store().is_empty() {
444 self.proposal_store_mut().empty();
446
447 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
449 }
450
451 Ok(())
452 }
453
454 pub fn extensions(&self) -> &Extensions<GroupContext> {
456 self.public_group().group_context().extensions()
457 }
458
459 pub fn ext_commit_sender_index(
461 &self,
462 commit: &StagedCommit,
463 ) -> Result<LeafNodeIndex, LibraryError> {
464 self.public_group().ext_commit_sender_index(commit)
465 }
466
467 pub fn load<Storage: crate::storage::StorageProvider>(
471 storage: &Storage,
472 group_id: &GroupId,
473 ) -> Result<Option<MlsGroup>, Storage::Error> {
474 let public_group = PublicGroup::load(storage, group_id)?;
475 let group_epoch_secrets = storage.group_epoch_secrets(group_id)?;
476 let own_leaf_index = storage.own_leaf_index(group_id)?;
477 let message_secrets_store = storage.message_secrets(group_id)?;
478 let resumption_psk_store = storage.resumption_psk_store(group_id)?;
479 let mls_group_config = storage.mls_group_join_config(group_id)?;
480 let own_leaf_nodes = storage.own_leaf_nodes(group_id)?;
481 let group_state = storage.group_state(group_id)?;
482 #[cfg(feature = "extensions-draft")]
483 let application_export_tree = storage.application_export_tree(group_id)?;
484
485 let build = || -> Option<Self> {
486 Some(Self {
487 public_group: public_group?,
488 group_epoch_secrets: group_epoch_secrets?,
489 own_leaf_index: own_leaf_index?,
490 message_secrets_store: message_secrets_store?,
491 resumption_psk_store: resumption_psk_store?,
492 mls_group_config: mls_group_config?,
493 own_leaf_nodes,
494 aad: vec![],
495 #[cfg(feature = "extensions-draft")]
496 safe_aad: SafeAad::empty(),
497 group_state: group_state?,
498 #[cfg(feature = "extensions-draft")]
499 application_export_tree,
500 })
501 };
502
503 Ok(build())
504 }
505
506 pub fn delete<Storage: crate::storage::StorageProvider>(
510 &mut self,
511 storage: &Storage,
512 ) -> Result<(), Storage::Error> {
513 PublicGroup::delete(storage, self.group_id())?;
514 storage.delete_own_leaf_index(self.group_id())?;
515 storage.delete_group_epoch_secrets(self.group_id())?;
516 storage.delete_message_secrets(self.group_id())?;
517 storage.delete_all_resumption_psk_secrets(self.group_id())?;
518 storage.delete_group_config(self.group_id())?;
519 storage.delete_own_leaf_nodes(self.group_id())?;
520 storage.delete_group_state(self.group_id())?;
521 storage.clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())?;
522
523 #[cfg(feature = "extensions-draft")]
524 storage.delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
525
526 #[cfg(feature = "virtual-clients-draft")]
531 {
532 storage.delete_vc_emulation_bindings(self.group_id())?;
533 storage.delete_registered_vc_emulation_epoch(self.group_id())?;
534 }
535
536 self.proposal_store_mut().empty();
537 storage.delete_encryption_epoch_key_pairs(
538 self.group_id(),
539 &self.epoch(),
540 self.own_leaf_index().u32(),
541 )?;
542
543 Ok(())
544 }
545
546 pub fn export_ratchet_tree(&self) -> RatchetTree {
550 self.public_group().export_ratchet_tree()
551 }
552}
553
554#[cfg(feature = "virtual-clients-draft")]
560#[derive(thiserror::Error, Debug, PartialEq, Clone)]
561pub(crate) enum VcEmulationStateError<StorageError> {
562 #[error("Error reading the binding or emulation-epoch state from storage: {0}")]
564 Storage(StorageError),
565 #[error("The group is bound to an emulation epoch, but its state is missing.")]
567 MissingEmulationEpochState,
568}
569
570impl MlsGroup {
572 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
574 self.public_group.required_capabilities()
575 }
576
577 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
579 &self.group_epoch_secrets
580 }
581
582 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
584 self.message_secrets_store.message_secrets()
585 }
586
587 pub(crate) fn resize_message_secrets_store(&mut self, policy: &PastEpochDeletionPolicy) {
591 self.message_secrets_store.resize(policy);
592 }
593
594 pub fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
596 self.mls_group_config.past_epoch_deletion_policy()
597 }
598
599 pub fn set_past_epoch_deletion_policy<Provider: OpenMlsProvider>(
601 &mut self,
602 provider: &Provider,
603 policy: PastEpochDeletionPolicy,
604 ) -> Result<(), SetPastEpochDeletionPolicyError<Provider::StorageError>> {
605 self.resize_message_secrets_store(&policy);
607
608 self.mls_group_config.past_epoch_deletion_policy = policy;
610
611 provider
613 .storage()
614 .write_mls_join_config(self.group_id(), &self.mls_group_config)?;
615
616 provider
618 .storage()
619 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
620
621 Ok(())
622 }
623
624 pub(crate) fn message_secrets_for_epoch_mut(
626 &mut self,
627 epoch: GroupEpoch,
628 ) -> Result<&mut MessageSecrets, SecretTreeError> {
629 if epoch < self.context().epoch() {
630 self.message_secrets_store
631 .secrets_for_epoch_mut(epoch)
632 .ok_or(SecretTreeError::TooDistantInThePast)
633 } else {
634 Ok(self.message_secrets_store.message_secrets_mut())
635 }
636 }
637
638 pub(crate) fn message_secrets_for_epoch(
640 &self,
641 epoch: GroupEpoch,
642 ) -> Result<&MessageSecrets, SecretTreeError> {
643 if epoch < self.context().epoch() {
644 self.message_secrets_store
645 .secrets_for_epoch(epoch)
646 .ok_or(SecretTreeError::TooDistantInThePast)
647 } else {
648 Ok(self.message_secrets_store.message_secrets())
649 }
650 }
651
652 pub(crate) fn message_secrets_and_leaves(
658 &self,
659 epoch: GroupEpoch,
660 ) -> Result<(&MessageSecrets, &[Member]), SecretTreeError> {
661 if epoch < self.context().epoch() {
662 self.message_secrets_store
663 .secrets_and_leaves_for_epoch(epoch)
664 .ok_or(SecretTreeError::TooDistantInThePast)
665 } else {
666 Ok((self.message_secrets_store.message_secrets(), &[]))
669 }
670 }
671
672 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
674 &self,
675 framing_parameters: FramingParameters,
676 extensions: Extensions<GroupContext>,
677 signer: &impl Signer,
678 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
679 {
680 let required_extension = extensions
682 .iter()
683 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
684 if let Some(required_extension) = required_extension {
685 let required_capabilities = required_extension.as_required_capabilities_extension()?;
686 self.own_leaf_node()
688 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
689 .capabilities()
690 .supports_required_capabilities(required_capabilities)?;
691
692 self.public_group()
695 .check_extension_support(required_capabilities.extension_types())?;
696 }
697 let proposal = GroupContextExtensionProposal::new(extensions);
698 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
699 AuthenticatedContent::member_proposal(
700 framing_parameters,
701 self.own_leaf_index(),
702 proposal,
703 self.context(),
704 signer,
705 )
706 .map_err(|e| e.into())
707 }
708
709 #[cfg(feature = "virtual-clients-draft")]
717 pub(crate) fn vc_emulation_state_at_epoch<Storage: StorageProvider>(
718 &self,
719 storage: &Storage,
720 epoch: GroupEpoch,
721 ) -> Result<
722 Option<crate::components::vc_derivation_info::EmulationEpochState>,
723 VcEmulationStateError<Storage::Error>,
724 > {
725 let bindings: Option<crate::components::vc_derivation_info::VcEmulationBindings> = storage
726 .vc_emulation_bindings(self.group_id())
727 .map_err(VcEmulationStateError::Storage)?;
728 let Some(epoch_id) = bindings.and_then(|bindings| bindings.get(epoch).cloned()) else {
729 return Ok(None);
730 };
731 let state = storage
732 .vc_emulation_epoch_state(&epoch_id)
733 .map_err(VcEmulationStateError::Storage)?
734 .ok_or_else(|| {
735 log::error!("vc: group is bound to emulation epoch, but state is missing");
736 VcEmulationStateError::MissingEmulationEpochState
737 })?;
738 Ok(Some(state))
739 }
740
741 pub(crate) fn encrypt<Provider: OpenMlsProvider>(
743 &mut self,
744 public_message: AuthenticatedContent,
745 provider: &Provider,
746 ) -> Result<EncryptionOutput, MessageEncryptionError<Provider::StorageError>> {
747 let padding_size = self.configuration().padding_size();
748
749 #[cfg(feature = "virtual-clients-draft")]
753 let emulation_state = self
754 .vc_emulation_state_at_epoch(provider.storage(), self.epoch())
755 .map_err(|e| match e {
756 VcEmulationStateError::Storage(e) => MessageEncryptionError::StorageError(e),
757 VcEmulationStateError::MissingEmulationEpochState => {
758 MessageEncryptionError::VirtualClientsError(
759 crate::components::vc_derivation_info::VirtualClientsError::MissingEmulationEpochState,
760 )
761 }
762 })?;
763 #[cfg(feature = "virtual-clients-draft")]
764 let emulator_ctx: Option<crate::framing::EmulatorReuseGuardCtx<'_>> = emulation_state
765 .as_ref()
766 .map(|state| state.reuse_guard_inputs());
767
768 let msg = PrivateMessage::try_from_authenticated_content(
769 provider.crypto(),
770 provider.rand(),
771 &public_message,
772 self.ciphersuite(),
773 self.message_secrets_store.message_secrets_mut(),
774 padding_size,
775 #[cfg(feature = "virtual-clients-draft")]
776 emulator_ctx.as_ref(),
777 )?;
778
779 #[cfg(feature = "virtual-clients-draft")]
784 let msg = {
785 use crate::components::vc_derivation_info::RatchetType;
786 let mut msg = msg;
787 if let Some(state) = &emulation_state {
788 let ratchet_type = match public_message.content().content_type() {
789 ContentType::Application => RatchetType::Application,
790 ContentType::Proposal | ContentType::Commit => RatchetType::Handshake,
791 };
792 let generation_id = state
793 .derive_generation_id(
794 provider.crypto(),
795 self.group_id(),
796 self.epoch(),
797 msg.generation,
798 ratchet_type,
799 )
800 .map_err(MessageEncryptionError::VirtualClientsError)?;
801 msg.generation_id = Some(generation_id);
802 }
803 msg
804 };
805
806 provider
807 .storage()
808 .write_message_secrets(self.group_id(), &self.message_secrets_store)
809 .map_err(MessageEncryptionError::StorageError)?;
810
811 Ok(msg)
812 }
813
814 pub(crate) fn outgoing_wire_format(&self) -> WireFormat {
816 self.mls_group_config.wire_format_policy().outgoing().into()
817 }
818
819 pub(crate) fn outgoing_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
825 #[cfg(feature = "extensions-draft")]
826 {
827 self.assembled_authenticated_data()
828 }
829 #[cfg(not(feature = "extensions-draft"))]
830 {
831 Ok(self.aad.clone())
832 }
833 }
834
835 #[cfg(feature = "extensions-draft")]
840 pub(crate) fn assembled_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
841 if !self.context().safe_aad_required() {
842 return Ok(self.aad.clone());
843 }
844 crate::framing::safe_aad::assemble_authenticated_data(&self.safe_aad, &self.aad)
845 .map_err(|_| LibraryError::custom("SafeAad serialization failed"))
846 }
847
848 pub fn delete_past_epoch_secrets<Provider: OpenMlsProvider>(
852 &mut self,
853 provider: &Provider,
854 policy: PastEpochDeletion,
855 ) -> Result<(), DeletePastEpochSecretsError<Provider::StorageError>> {
856 self.message_secrets_store.delete_past_epoch_secrets(policy);
858 provider
860 .storage()
861 .write_message_secrets(self.group_id(), &self.message_secrets_store)?;
862
863 Ok(())
864 }
865
866 pub fn proposal_store(&self) -> &ProposalStore {
868 self.public_group.proposal_store()
869 }
870
871 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
873 self.public_group.proposal_store_mut()
874 }
875
876 pub(crate) fn context(&self) -> &GroupContext {
878 self.public_group.group_context()
879 }
880
881 pub(crate) fn version(&self) -> ProtocolVersion {
883 self.public_group.version()
884 }
885
886 #[inline]
888 pub(crate) fn reset_aad(&mut self) {
889 self.aad.clear();
890 #[cfg(feature = "extensions-draft")]
891 {
892 self.safe_aad = SafeAad::empty();
893 }
894 }
895
896 pub fn public_group(&self) -> &PublicGroup {
898 &self.public_group
899 }
900}
901
902#[cfg(feature = "virtual-clients-draft")]
913#[derive(Debug, Clone)]
914pub struct HandshakeConfirmationData {
915 pub epoch: GroupEpoch,
918 pub generation: u32,
920 pub generation_id: Option<crate::components::vc_derivation_info::GenerationId>,
925}
926
927pub(crate) struct HandshakeFramingOutput {
935 pub(crate) message: MlsMessageOut,
936 #[cfg(feature = "virtual-clients-draft")]
937 pub(crate) confirmation: Option<HandshakeConfirmationData>,
938}
939
940impl MlsGroup {
942 pub(super) fn store_epoch_keypairs<Storage: StorageProvider>(
947 &self,
948 store: &Storage,
949 keypair_references: &[EncryptionKeyPair],
950 ) -> Result<(), Storage::Error> {
951 store.write_encryption_epoch_key_pairs(
952 self.group_id(),
953 &self.context().epoch(),
954 self.own_leaf_index().u32(),
955 keypair_references,
956 )
957 }
958
959 pub(super) fn read_epoch_keypairs<Storage: StorageProvider>(
964 &self,
965 store: &Storage,
966 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
967 store.encryption_epoch_key_pairs(
968 self.group_id(),
969 &self.context().epoch(),
970 self.own_leaf_index().u32(),
971 )
972 }
973
974 #[cfg(not(feature = "virtual-clients-draft"))]
979 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
980 &self,
981 store: &Storage,
982 ) -> Result<(), Storage::Error> {
983 store.delete_encryption_epoch_key_pairs(
984 self.group_id(),
985 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
986 self.own_leaf_index().u32(),
987 )
988 }
989
990 #[cfg(feature = "virtual-clients-draft")]
991 pub(super) fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
992 &self,
993 store: &Storage,
994 previous_own_leaf_index: LeafNodeIndex,
995 ) -> Result<(), Storage::Error> {
996 store.delete_encryption_epoch_key_pairs(
1002 self.group_id(),
1003 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
1004 previous_own_leaf_index.u32(),
1005 )
1006 }
1007
1008 pub(super) fn store<Storage: crate::storage::StorageProvider>(
1011 &self,
1012 storage: &Storage,
1013 ) -> Result<(), Storage::Error> {
1014 self.public_group.store(storage)?;
1015 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
1016 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
1017 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
1018 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
1019 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
1020 storage.write_group_state(self.group_id(), &self.group_state)?;
1021 #[cfg(feature = "extensions-draft")]
1022 if let Some(application_export_tree) = &self.application_export_tree {
1023 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
1024 }
1025
1026 Ok(())
1027 }
1028
1029 fn content_to_mls_message(
1033 &mut self,
1034 mls_auth_content: AuthenticatedContent,
1035 provider: &impl OpenMlsProvider,
1036 ) -> Result<HandshakeFramingOutput, LibraryError> {
1037 let output = match self.configuration().wire_format_policy().outgoing() {
1038 OutgoingWireFormatPolicy::AlwaysPlaintext => {
1039 let mut plaintext: PublicMessage = mls_auth_content.into();
1040 if plaintext.sender().is_member() {
1042 plaintext.set_membership_tag(
1043 provider.crypto(),
1044 self.ciphersuite(),
1045 self.message_secrets().membership_key(),
1046 self.message_secrets().serialized_context(),
1047 )?;
1048 }
1049 HandshakeFramingOutput {
1050 message: plaintext.into(),
1051 #[cfg(feature = "virtual-clients-draft")]
1052 confirmation: None,
1053 }
1054 }
1055 OutgoingWireFormatPolicy::AlwaysCiphertext => {
1056 #[cfg(feature = "virtual-clients-draft")]
1060 let epoch = self.epoch();
1061 let encryption_output = self
1062 .encrypt(mls_auth_content, provider)
1063 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
1065 let message = MlsMessageOut::from_private_message(
1066 encryption_output.private_message,
1067 self.version(),
1068 );
1069 HandshakeFramingOutput {
1070 message,
1071 #[cfg(feature = "virtual-clients-draft")]
1072 confirmation: Some(HandshakeConfirmationData {
1073 epoch,
1074 generation: encryption_output.generation,
1075 generation_id: encryption_output.generation_id,
1076 }),
1077 }
1078 }
1079 };
1080 Ok(output)
1081 }
1082
1083 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
1086 match self.group_state {
1087 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
1088 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
1089 MlsGroupState::Operational => Ok(()),
1090 }
1091 }
1092}
1093
1094impl MlsGroup {
1096 #[cfg(any(feature = "test-utils", test))]
1097 pub fn export_group_context(&self) -> &GroupContext {
1098 self.context()
1099 }
1100
1101 #[cfg(any(feature = "test-utils", test))]
1102 pub fn tree_hash(&self) -> &[u8] {
1103 self.public_group().group_context().tree_hash()
1104 }
1105
1106 #[cfg(any(feature = "test-utils", test))]
1107 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
1108 self.message_secrets_store.message_secrets_mut()
1109 }
1110
1111 #[cfg(any(feature = "test-utils", test))]
1112 pub fn print_ratchet_tree(&self, message: &str) {
1113 println!("{}: {}", message, self.public_group().export_ratchet_tree());
1114 }
1115
1116 #[cfg(any(feature = "test-utils", test))]
1117 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
1118 self.public_group.context_mut()
1119 }
1120
1121 #[cfg(test)]
1122 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
1123 self.own_leaf_index = own_leaf_index;
1124 }
1125
1126 #[cfg(test)]
1127 pub(crate) fn own_tree_position(&self) -> TreePosition {
1128 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
1129 }
1130
1131 #[cfg(test)]
1132 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
1133 &self.message_secrets_store
1134 }
1135
1136 #[cfg(test)]
1137 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
1138 &self.resumption_psk_store
1139 }
1140
1141 #[cfg(test)]
1142 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
1143 self.public_group.set_group_context(group_context)
1144 }
1145
1146 #[cfg(any(test, feature = "test-utils"))]
1147 pub fn ensure_persistence(&self, storage: &impl StorageProvider) -> Result<(), LibraryError> {
1148 let loaded = MlsGroup::load(storage, self.group_id())
1149 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
1150 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
1151
1152 if self != &other {
1153 let mut diagnostics = Vec::new();
1154
1155 if self.mls_group_config != other.mls_group_config {
1156 diagnostics.push(format!(
1157 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
1158 self.mls_group_config, other.mls_group_config
1159 ));
1160 }
1161 if self.public_group != other.public_group {
1162 diagnostics.push(format!(
1163 "public_group:\n Current: {:?}\n Loaded: {:?}",
1164 self.public_group, other.public_group
1165 ));
1166 }
1167 if self.group_epoch_secrets != other.group_epoch_secrets {
1168 diagnostics.push(format!(
1169 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
1170 self.group_epoch_secrets, other.group_epoch_secrets
1171 ));
1172 }
1173 if self.own_leaf_index != other.own_leaf_index {
1174 diagnostics.push(format!(
1175 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
1176 self.own_leaf_index, other.own_leaf_index
1177 ));
1178 }
1179 if self.message_secrets_store != other.message_secrets_store {
1180 diagnostics.push(format!(
1181 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
1182 self.message_secrets_store, other.message_secrets_store
1183 ));
1184 }
1185 if self.resumption_psk_store != other.resumption_psk_store {
1186 diagnostics.push(format!(
1187 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
1188 self.resumption_psk_store, other.resumption_psk_store
1189 ));
1190 }
1191 if self.own_leaf_nodes != other.own_leaf_nodes {
1192 diagnostics.push(format!(
1193 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
1194 self.own_leaf_nodes, other.own_leaf_nodes
1195 ));
1196 }
1197 if self.aad != other.aad {
1198 diagnostics.push(format!(
1199 "aad:\n Current: {:?}\n Loaded: {:?}",
1200 self.aad, other.aad
1201 ));
1202 }
1203 if self.group_state != other.group_state {
1204 diagnostics.push(format!(
1205 "group_state:\n Current: {:?}\n Loaded: {:?}",
1206 self.group_state, other.group_state
1207 ));
1208 }
1209 #[cfg(feature = "extensions-draft")]
1210 if self.application_export_tree != other.application_export_tree {
1211 diagnostics.push(format!(
1212 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
1213 self.application_export_tree, other.application_export_tree
1214 ));
1215 }
1216
1217 log::error!(
1218 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
1219 diagnostics.len(),
1220 diagnostics.join("\n\n")
1221 );
1222
1223 return Err(LibraryError::custom(
1224 "Loaded group does not match current group",
1225 ));
1226 }
1227
1228 Ok(())
1229 }
1230}
1231
1232#[derive(Debug)]
1235pub struct StagedWelcome {
1236 mls_group_config: MlsGroupJoinConfig,
1238 public_group: PublicGroup,
1239 group_epoch_secrets: GroupEpochSecrets,
1240 own_leaf_index: LeafNodeIndex,
1241
1242 message_secrets_store: MessageSecretsStore,
1249
1250 #[cfg(feature = "extensions-draft")]
1253 application_export_secret: ApplicationExportSecret,
1254
1255 resumption_psk_store: ResumptionPskStore,
1257
1258 verifiable_group_info: VerifiableGroupInfo,
1260
1261 key_material: WelcomeKeyMaterial,
1263
1264 path_keypairs: Option<Vec<EncryptionKeyPair>>,
1266}
1267
1268pub struct ProcessedWelcome {
1275 mls_group_config: MlsGroupJoinConfig,
1277
1278 ciphersuite: Ciphersuite,
1281 group_secrets: GroupSecrets,
1282 epoch_secrets: crate::schedule::EpochSecretsResult,
1283 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
1284 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
1285 key_material: WelcomeKeyMaterial,
1286}
1287
1288#[derive(Debug)]
1297pub(crate) enum WelcomeKeyMaterial {
1298 KeyPackage(Box<KeyPackageBundle>),
1301 #[cfg(feature = "virtual-clients-draft")]
1304 VirtualClient(crate::components::vc_derivation_info::VcWelcomeMaterial),
1305}
1306
1307impl WelcomeKeyMaterial {
1308 fn key_package_ref(
1314 &self,
1315 crypto: &impl OpenMlsCrypto,
1316 ) -> Result<crate::ciphersuite::hash_ref::KeyPackageRef, LibraryError> {
1317 match self {
1318 WelcomeKeyMaterial::KeyPackage(bundle) => bundle.key_package().hash_ref(crypto),
1319 #[cfg(feature = "virtual-clients-draft")]
1320 WelcomeKeyMaterial::VirtualClient(material) => Ok(material.key_package_ref.clone()),
1321 }
1322 }
1323
1324 fn init_private_key(&self) -> &crate::ciphersuite::HpkePrivateKey {
1326 match self {
1327 WelcomeKeyMaterial::KeyPackage(bundle) => bundle.init_private_key(),
1328 #[cfg(feature = "virtual-clients-draft")]
1329 WelcomeKeyMaterial::VirtualClient(material) => &material.init_private_key,
1330 }
1331 }
1332
1333 fn key_package_bundle(&self) -> Option<&KeyPackageBundle> {
1337 match self {
1338 WelcomeKeyMaterial::KeyPackage(bundle) => Some(bundle),
1339 #[cfg(feature = "virtual-clients-draft")]
1340 WelcomeKeyMaterial::VirtualClient(_) => None,
1341 }
1342 }
1343
1344 fn encryption_key_pair(&self) -> EncryptionKeyPair {
1346 match self {
1347 WelcomeKeyMaterial::KeyPackage(bundle) => bundle.encryption_key_pair(),
1348 #[cfg(feature = "virtual-clients-draft")]
1349 WelcomeKeyMaterial::VirtualClient(material) => material.encryption_keypair.clone(),
1350 }
1351 }
1352}