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::{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 #[openmls_traits::maybe_async]
339 pub async fn set_configuration<Storage: StorageProvider>(
340 &mut self,
341 storage: &Storage,
342 mls_group_config: &MlsGroupJoinConfig,
343 ) -> Result<(), Storage::Error> {
344 let policy_changed = self.mls_group_config.past_epoch_deletion_policy()
345 != mls_group_config.past_epoch_deletion_policy();
346 #[cfg(feature = "virtual-clients-draft")]
347 let retention_changed = self.mls_group_config.vc_derivation_epoch_retention_policy()
348 != mls_group_config.vc_derivation_epoch_retention_policy();
349
350 self.mls_group_config = mls_group_config.clone();
351 storage
352 .write_mls_join_config(self.group_id(), mls_group_config)
353 .await?;
354
355 if policy_changed {
356 self.resize_message_secrets_store(mls_group_config.past_epoch_deletion_policy());
358 storage
359 .write_message_secrets(self.group_id(), &self.message_secrets_store)
360 .await?;
361 }
362
363 #[cfg(feature = "virtual-clients-draft")]
364 if retention_changed {
365 self.apply_vc_derivation_epoch_retention(storage).await?;
366 }
367
368 Ok(())
369 }
370
371 pub fn set_aad(&mut self, aad: Vec<u8>) {
375 self.aad = aad;
376 }
377
378 pub fn aad(&self) -> &[u8] {
381 &self.aad
382 }
383
384 #[cfg(feature = "extensions-draft")]
394 pub fn set_safe_aad(&mut self, items: Vec<SafeAadItem>) -> Result<(), SafeAadError> {
395 self.safe_aad = SafeAad::from_items(items)?;
396 Ok(())
397 }
398
399 #[cfg(feature = "extensions-draft")]
402 pub fn safe_aad_items(&self) -> &[SafeAadItem] {
403 self.safe_aad.items()
404 }
405
406 pub fn ciphersuite(&self) -> Ciphersuite {
410 self.public_group.ciphersuite()
411 }
412
413 pub fn confirmation_tag(&self) -> &ConfirmationTag {
415 self.public_group.confirmation_tag()
416 }
417
418 pub fn is_active(&self) -> bool {
421 !matches!(self.group_state, MlsGroupState::Inactive)
422 }
423
424 pub fn credential(&self) -> Result<&Credential, MlsGroupStateError> {
427 if !self.is_active() {
428 return Err(MlsGroupStateError::UseAfterEviction);
429 }
430 self.public_group
431 .leaf(self.own_leaf_index())
432 .map(|node| node.credential())
433 .ok_or_else(|| LibraryError::custom("Own leaf node missing").into())
434 }
435
436 pub fn own_leaf_index(&self) -> LeafNodeIndex {
438 self.own_leaf_index
439 }
440
441 pub fn own_leaf_node(&self) -> Option<&LeafNode> {
443 self.public_group().leaf(self.own_leaf_index())
444 }
445
446 pub fn group_id(&self) -> &GroupId {
448 self.public_group.group_id()
449 }
450
451 pub fn epoch(&self) -> GroupEpoch {
453 self.public_group.group_context().epoch()
454 }
455
456 pub fn pending_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
458 self.proposal_store().proposals()
459 }
460
461 pub fn treesync(&self) -> &TreeSync {
463 self.public_group.treesync()
464 }
465
466 pub fn pending_commit(&self) -> Option<&StagedCommit> {
470 match self.group_state {
471 MlsGroupState::PendingCommit(ref pending_commit_state) => {
472 Some(pending_commit_state.staged_commit())
473 }
474 MlsGroupState::Operational => None,
475 MlsGroupState::Inactive => None,
476 }
477 }
478
479 #[openmls_traits::maybe_async]
491 pub async fn clear_pending_commit<Storage: StorageProvider>(
492 &mut self,
493 storage: &Storage,
494 ) -> Result<(), Storage::Error> {
495 match self.group_state {
496 MlsGroupState::PendingCommit(ref pending_commit_state) => {
497 if let PendingCommitState::Member(_) = **pending_commit_state {
498 self.group_state = MlsGroupState::Operational;
499 storage
500 .write_group_state(self.group_id(), &self.group_state)
501 .await
502 } else {
503 Ok(())
504 }
505 }
506 MlsGroupState::Operational | MlsGroupState::Inactive => Ok(()),
507 }
508 }
509
510 #[openmls_traits::maybe_async]
517 pub async fn clear_pending_proposals<Storage: StorageProvider>(
518 &mut self,
519 storage: &Storage,
520 ) -> Result<(), Storage::Error> {
521 if !self.proposal_store().is_empty() {
523 self.proposal_store_mut().empty();
525
526 storage
528 .clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())
529 .await?;
530 }
531
532 Ok(())
533 }
534
535 pub fn extensions(&self) -> &Extensions<GroupContext> {
537 self.public_group().group_context().extensions()
538 }
539
540 pub fn ext_commit_sender_index(
542 &self,
543 commit: &StagedCommit,
544 ) -> Result<LeafNodeIndex, LibraryError> {
545 self.public_group().ext_commit_sender_index(commit)
546 }
547
548 #[openmls_traits::maybe_async]
552 pub async fn load<Storage: crate::storage::StorageProvider>(
553 storage: &Storage,
554 group_id: &GroupId,
555 ) -> Result<Option<MlsGroup>, Storage::Error> {
556 let public_group = PublicGroup::load(storage, group_id).await?;
557 let group_epoch_secrets = storage.group_epoch_secrets(group_id).await?;
558 let own_leaf_index = storage.own_leaf_index(group_id).await?;
559 let message_secrets_store = storage.message_secrets(group_id).await?;
560 let resumption_psk_store = storage.resumption_psk_store(group_id).await?;
561 let mls_group_config = storage.mls_group_join_config(group_id).await?;
562 let own_leaf_nodes = storage.own_leaf_nodes(group_id).await?;
563 let group_state = storage.group_state(group_id).await?;
564 #[cfg(feature = "extensions-draft")]
565 let application_export_tree = storage.application_export_tree(group_id).await?;
566 #[cfg(feature = "virtual-clients-draft")]
570 let emulation_group =
571 crate::components::vc_derivation_info::newest_vc_derivation_epoch(storage, group_id)
572 .await?
573 .is_some();
574
575 let build = || -> Option<Self> {
576 Some(Self {
577 public_group: public_group?,
578 group_epoch_secrets: group_epoch_secrets?,
579 own_leaf_index: own_leaf_index?,
580 message_secrets_store: message_secrets_store?,
581 resumption_psk_store: resumption_psk_store?,
582 mls_group_config: mls_group_config?,
583 own_leaf_nodes,
584 aad: vec![],
585 #[cfg(feature = "extensions-draft")]
586 safe_aad: SafeAad::empty(),
587 group_state: group_state?,
588 #[cfg(feature = "extensions-draft")]
589 application_export_tree,
590 #[cfg(feature = "virtual-clients-draft")]
591 emulation_group,
592 })
593 };
594
595 Ok(build())
596 }
597
598 #[openmls_traits::maybe_async]
602 pub async fn delete<Storage: crate::storage::StorageProvider>(
603 &mut self,
604 storage: &Storage,
605 ) -> Result<(), Storage::Error> {
606 PublicGroup::delete(storage, self.group_id()).await?;
607 storage.delete_own_leaf_index(self.group_id()).await?;
608 storage.delete_group_epoch_secrets(self.group_id()).await?;
609 storage.delete_message_secrets(self.group_id()).await?;
610 storage
611 .delete_all_resumption_psk_secrets(self.group_id())
612 .await?;
613 storage.delete_group_config(self.group_id()).await?;
614 storage.delete_own_leaf_nodes(self.group_id()).await?;
615 storage.delete_group_state(self.group_id()).await?;
616 storage
617 .clear_proposal_queue::<GroupId, ProposalRef>(self.group_id())
618 .await?;
619
620 #[cfg(feature = "extensions-draft")]
621 storage
622 .delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())
623 .await?;
624
625 #[cfg(feature = "virtual-clients-draft")]
628 self.drop_all_vc_derivation_epoch_references(storage)
629 .await?;
630
631 self.proposal_store_mut().empty();
632 storage
633 .delete_encryption_epoch_key_pairs(
634 self.group_id(),
635 &self.epoch(),
636 self.own_leaf_index().u32(),
637 )
638 .await?;
639
640 Ok(())
641 }
642
643 pub fn export_ratchet_tree(&self) -> RatchetTree {
647 self.public_group().export_ratchet_tree()
648 }
649}
650
651#[cfg(feature = "virtual-clients-draft")]
657#[derive(thiserror::Error, Debug, PartialEq, Clone)]
658pub(crate) enum VcDerivationStateError<StorageError> {
659 #[error("Error reading the binding or derivation-epoch state from storage: {0}")]
661 Storage(StorageError),
662 #[error("The group is bound to a derivation epoch, but its state is missing.")]
664 MissingDerivationEpochState,
665}
666
667impl MlsGroup {
669 pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
671 self.public_group.required_capabilities()
672 }
673
674 pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
676 &self.group_epoch_secrets
677 }
678
679 pub(crate) fn message_secrets(&self) -> &MessageSecrets {
681 self.message_secrets_store.message_secrets()
682 }
683
684 pub(crate) fn resize_message_secrets_store(&mut self, policy: &PastEpochDeletionPolicy) {
688 self.message_secrets_store.resize(policy);
689 }
690
691 pub fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
693 self.mls_group_config.past_epoch_deletion_policy()
694 }
695
696 #[openmls_traits::maybe_async]
698 pub async fn set_past_epoch_deletion_policy<Provider: OpenMlsProvider>(
699 &mut self,
700 provider: &Provider,
701 policy: PastEpochDeletionPolicy,
702 ) -> Result<(), SetPastEpochDeletionPolicyError<Provider::StorageError>> {
703 self.resize_message_secrets_store(&policy);
705
706 self.mls_group_config.past_epoch_deletion_policy = policy;
708
709 provider
711 .storage()
712 .write_mls_join_config(self.group_id(), &self.mls_group_config)
713 .await?;
714
715 provider
717 .storage()
718 .write_message_secrets(self.group_id(), &self.message_secrets_store)
719 .await?;
720
721 Ok(())
722 }
723
724 #[cfg(feature = "virtual-clients-draft")]
726 pub fn vc_derivation_epoch_retention_policy(&self) -> &VcDerivationEpochRetentionPolicy {
727 self.mls_group_config.vc_derivation_epoch_retention_policy()
728 }
729
730 #[cfg(feature = "virtual-clients-draft")]
733 #[openmls_traits::maybe_async]
734 pub async fn set_vc_derivation_epoch_retention_policy<Provider: OpenMlsProvider>(
735 &mut self,
736 provider: &Provider,
737 policy: VcDerivationEpochRetentionPolicy,
738 ) -> Result<(), Provider::StorageError> {
739 self.mls_group_config.vc_derivation_epoch_retention_policy = policy;
740 provider
741 .storage()
742 .write_mls_join_config(self.group_id(), &self.mls_group_config)
743 .await?;
744 self.apply_vc_derivation_epoch_retention(provider.storage())
745 .await
746 }
747
748 pub(crate) fn message_secrets_for_epoch_mut(
750 &mut self,
751 epoch: GroupEpoch,
752 ) -> Result<&mut MessageSecrets, SecretTreeError> {
753 if epoch < self.context().epoch() {
754 self.message_secrets_store
755 .secrets_for_epoch_mut(epoch)
756 .ok_or(SecretTreeError::TooDistantInThePast)
757 } else {
758 Ok(self.message_secrets_store.message_secrets_mut())
759 }
760 }
761
762 pub(crate) fn message_secrets_for_epoch(
764 &self,
765 epoch: GroupEpoch,
766 ) -> Result<&MessageSecrets, SecretTreeError> {
767 if epoch < self.context().epoch() {
768 self.message_secrets_store
769 .secrets_for_epoch(epoch)
770 .ok_or(SecretTreeError::TooDistantInThePast)
771 } else {
772 Ok(self.message_secrets_store.message_secrets())
773 }
774 }
775
776 pub(crate) fn message_secrets_and_leaves(
782 &self,
783 epoch: GroupEpoch,
784 ) -> Result<(&MessageSecrets, &[Member]), SecretTreeError> {
785 if epoch < self.context().epoch() {
786 self.message_secrets_store
787 .secrets_and_leaves_for_epoch(epoch)
788 .ok_or(SecretTreeError::TooDistantInThePast)
789 } else {
790 Ok((self.message_secrets_store.message_secrets(), &[]))
793 }
794 }
795
796 pub(crate) fn create_group_context_ext_proposal<Provider: OpenMlsProvider>(
798 &self,
799 framing_parameters: FramingParameters,
800 extensions: Extensions<GroupContext>,
801 signer: &impl Signer,
802 ) -> Result<AuthenticatedContent, CreateGroupContextExtProposalError<Provider::StorageError>>
803 {
804 let required_extension = extensions
806 .iter()
807 .find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
808 if let Some(required_extension) = required_extension {
809 let required_capabilities = required_extension.as_required_capabilities_extension()?;
810 self.own_leaf_node()
812 .ok_or_else(|| LibraryError::custom("Tree has no own leaf."))?
813 .capabilities()
814 .supports_required_capabilities(required_capabilities)?;
815
816 self.public_group()
819 .check_extension_support(required_capabilities.extension_types())?;
820 }
821 let proposal = GroupContextExtensionProposal::new(extensions);
822 let proposal = Proposal::GroupContextExtensions(Box::new(proposal));
823 AuthenticatedContent::member_proposal(
824 framing_parameters,
825 self.own_leaf_index(),
826 proposal,
827 self.context(),
828 signer,
829 )
830 .map_err(|e| e.into())
831 }
832
833 #[cfg(feature = "virtual-clients-draft")]
841 #[openmls_traits::maybe_async]
842 pub(crate) async fn vc_derivation_state_at_epoch<Storage: StorageProvider>(
843 &self,
844 storage: &Storage,
845 epoch: GroupEpoch,
846 ) -> Result<
847 Option<crate::components::vc_derivation_info::VcDerivationEpochState>,
848 VcDerivationStateError<Storage::Error>,
849 > {
850 let binding: Option<crate::components::vc_derivation_info::VcEmulationBinding> = storage
851 .vc_emulation_binding(self.group_id(), &epoch)
852 .await
853 .map_err(VcDerivationStateError::Storage)?;
854 let Some(epoch_id) = binding.map(|binding| binding.into_epoch_id()) else {
855 return Ok(None);
856 };
857 let state = storage
858 .vc_derivation_epoch_state(&epoch_id)
859 .await
860 .map_err(VcDerivationStateError::Storage)?
861 .ok_or_else(|| {
862 log::error!("vc: group is bound to derivation epoch, but state is missing");
863 VcDerivationStateError::MissingDerivationEpochState
864 })?;
865 Ok(Some(state))
866 }
867
868 #[cfg(feature = "virtual-clients-draft")]
874 #[openmls_traits::maybe_async]
875 pub async fn vc_derivation_epoch_at<Storage: StorageProvider>(
876 &self,
877 storage: &Storage,
878 epoch: GroupEpoch,
879 ) -> Result<Option<crate::components::vc_derivation_info::EpochId>, Storage::Error> {
880 let binding: Option<crate::components::vc_derivation_info::VcEmulationBinding> = storage
881 .vc_emulation_binding(self.group_id(), &epoch)
882 .await?;
883 Ok(binding.map(|binding| binding.into_epoch_id()))
884 }
885
886 #[cfg(feature = "virtual-clients-draft")]
894 pub fn is_emulation_group(&self) -> bool {
895 self.emulation_group
896 }
897
898 #[cfg(feature = "virtual-clients-draft")]
913 #[openmls_traits::maybe_async]
914 pub async fn newest_vc_derivation_epoch<Storage: StorageProvider>(
915 &self,
916 storage: &Storage,
917 ) -> Result<Option<crate::components::vc_derivation_info::EpochId>, Storage::Error> {
918 crate::components::vc_derivation_info::newest_vc_derivation_epoch(storage, self.group_id())
919 .await
920 }
921
922 #[cfg(feature = "virtual-clients-draft")]
929 #[openmls_traits::maybe_async]
930 pub async fn delete_vc_derivation_epochs<Provider: OpenMlsProvider>(
931 &self,
932 provider: &Provider,
933 deletion: VcDerivationEpochDeletion,
934 ) -> Result<VcDerivationEpochDeletionResult, Provider::StorageError> {
935 use crate::components::vc_derivation_info::VcDerivationEpochLog;
936
937 let storage = provider.storage();
938 let mut log = VcDerivationEpochLog::load(storage, self.group_id()).await?;
939 if log.is_empty() {
940 return Ok(VcDerivationEpochDeletionResult::default());
941 }
942 let cutoff = match deletion.time {
943 VcDerivationEpochDeletionTime::BeforeTimestamp(timestamp) => timestamp,
944 VcDerivationEpochDeletionTime::OlderThanDuration(duration) => SystemTime::now()
948 .checked_sub(duration)
949 .unwrap_or(SystemTime::UNIX_EPOCH),
950 };
951 let mut dropped = log.drop_superseded_before(cutoff);
952 if let Some(max_epochs) = deletion.max_epochs {
953 dropped.extend(log.shrink_to(max_epochs));
954 }
955 self.release_vc_derivation_epochs(storage, dropped).await
956 }
957
958 #[cfg(feature = "virtual-clients-draft")]
961 #[openmls_traits::maybe_async]
962 async fn apply_vc_derivation_epoch_retention<Storage: StorageProvider>(
963 &self,
964 storage: &Storage,
965 ) -> Result<(), Storage::Error> {
966 use crate::components::vc_derivation_info::VcDerivationEpochLog;
967
968 let mut log = VcDerivationEpochLog::load(storage, self.group_id()).await?;
969 let max_epochs = self
970 .mls_group_config
971 .vc_derivation_epoch_retention_policy()
972 .max_epochs()
973 .unwrap_or(usize::MAX);
974 let dropped = log.shrink_to(max_epochs);
975 if dropped.is_empty() {
976 return Ok(());
977 }
978 self.release_vc_derivation_epochs(storage, dropped).await?;
979 Ok(())
980 }
981
982 #[cfg(feature = "virtual-clients-draft")]
986 #[openmls_traits::maybe_async]
987 async fn release_vc_derivation_epochs<Storage: StorageProvider>(
988 &self,
989 storage: &Storage,
990 dropped: Vec<crate::components::vc_derivation_info::EpochId>,
991 ) -> Result<VcDerivationEpochDeletionResult, Storage::Error> {
992 use crate::components::vc_derivation_info::{EpochId, VcDerivationEpochState};
993
994 storage
995 .delete_vc_derivation_epoch_log_entries(self.group_id(), &dropped)
996 .await?;
997 let swept: Vec<EpochId> = storage
998 .delete_unreferenced_vc_derivation_epoch_states()
999 .await?;
1000 let mut result = VcDerivationEpochDeletionResult::default();
1001 for epoch_id in dropped {
1002 if swept.contains(&epoch_id) {
1003 result.deleted.push(epoch_id);
1004 continue;
1005 }
1006 let state: Option<VcDerivationEpochState> =
1009 storage.vc_derivation_epoch_state(&epoch_id).await?;
1010 if state.is_some() {
1011 result.kept.push(epoch_id);
1012 }
1013 }
1014 Ok(result)
1015 }
1016
1017 #[cfg(feature = "virtual-clients-draft")]
1021 #[openmls_traits::maybe_async]
1022 async fn drop_all_vc_derivation_epoch_references<Storage: StorageProvider>(
1023 &self,
1024 storage: &Storage,
1025 ) -> Result<(), Storage::Error> {
1026 use crate::components::vc_derivation_info::EpochId;
1027
1028 storage
1029 .delete_all_vc_emulation_bindings(self.group_id())
1030 .await?;
1031 storage
1032 .delete_vc_derivation_epoch_log(self.group_id())
1033 .await?;
1034 storage
1035 .delete_unreferenced_vc_derivation_epoch_states::<EpochId>()
1036 .await?;
1037 Ok(())
1038 }
1039
1040 #[openmls_traits::maybe_async]
1042 pub(crate) async fn encrypt<Provider: OpenMlsProvider>(
1043 &mut self,
1044 public_message: AuthenticatedContent,
1045 provider: &Provider,
1046 ) -> Result<EncryptionOutput, MessageEncryptionError<Provider::StorageError>> {
1047 let padding_size = self.configuration().padding_size();
1048
1049 #[cfg(feature = "virtual-clients-draft")]
1053 let derivation_state = self
1054 .vc_derivation_state_at_epoch(provider.storage(), self.epoch())
1055 .await
1056 .map_err(|e| match e {
1057 VcDerivationStateError::Storage(e) => MessageEncryptionError::StorageError(e),
1058 VcDerivationStateError::MissingDerivationEpochState => {
1059 MessageEncryptionError::VirtualClientsError(
1060 crate::components::vc_derivation_info::VirtualClientsError::MissingDerivationEpochState,
1061 )
1062 }
1063 })?;
1064 #[cfg(feature = "virtual-clients-draft")]
1065 let emulator_ctx: Option<crate::framing::EmulatorReuseGuardCtx<'_>> = derivation_state
1066 .as_ref()
1067 .map(|state| state.reuse_guard_inputs());
1068
1069 let msg = PrivateMessage::try_from_authenticated_content(
1070 provider.crypto(),
1071 provider.rand(),
1072 &public_message,
1073 self.ciphersuite(),
1074 self.message_secrets_store.message_secrets_mut(),
1075 padding_size,
1076 #[cfg(feature = "virtual-clients-draft")]
1077 emulator_ctx.as_ref(),
1078 )?;
1079
1080 #[cfg(feature = "virtual-clients-draft")]
1085 let msg = {
1086 use crate::components::vc_derivation_info::RatchetType;
1087 let mut msg = msg;
1088 if let Some(state) = &derivation_state {
1089 let ratchet_type = match public_message.content().content_type() {
1090 ContentType::Application => RatchetType::Application,
1091 ContentType::Proposal | ContentType::Commit => RatchetType::Handshake,
1092 };
1093 let generation_id = state
1094 .derive_generation_id(
1095 provider.crypto(),
1096 self.group_id(),
1097 self.epoch(),
1098 msg.generation,
1099 ratchet_type,
1100 )
1101 .map_err(MessageEncryptionError::VirtualClientsError)?;
1102 msg.generation_id = Some(generation_id);
1103 }
1104 msg
1105 };
1106
1107 provider
1108 .storage()
1109 .write_message_secrets(self.group_id(), &self.message_secrets_store)
1110 .await
1111 .map_err(MessageEncryptionError::StorageError)?;
1112
1113 Ok(msg)
1114 }
1115
1116 pub(crate) fn outgoing_wire_format(&self) -> WireFormat {
1118 self.mls_group_config.wire_format_policy().outgoing().into()
1119 }
1120
1121 pub(crate) fn outgoing_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
1127 #[cfg(feature = "extensions-draft")]
1128 {
1129 self.assembled_authenticated_data()
1130 }
1131 #[cfg(not(feature = "extensions-draft"))]
1132 {
1133 Ok(self.aad.clone())
1134 }
1135 }
1136
1137 #[cfg(feature = "extensions-draft")]
1142 pub(crate) fn assembled_authenticated_data(&self) -> Result<Vec<u8>, LibraryError> {
1143 if !self.context().safe_aad_required() {
1144 return Ok(self.aad.clone());
1145 }
1146 crate::framing::safe_aad::assemble_authenticated_data(&self.safe_aad, &self.aad)
1147 .map_err(|_| LibraryError::custom("SafeAad serialization failed"))
1148 }
1149
1150 #[openmls_traits::maybe_async]
1154 pub async fn delete_past_epoch_secrets<Provider: OpenMlsProvider>(
1155 &mut self,
1156 provider: &Provider,
1157 policy: PastEpochDeletion,
1158 ) -> Result<(), DeletePastEpochSecretsError<Provider::StorageError>> {
1159 self.message_secrets_store.delete_past_epoch_secrets(policy);
1161 provider
1163 .storage()
1164 .write_message_secrets(self.group_id(), &self.message_secrets_store)
1165 .await?;
1166
1167 Ok(())
1168 }
1169
1170 pub fn proposal_store(&self) -> &ProposalStore {
1172 self.public_group.proposal_store()
1173 }
1174
1175 pub(crate) fn proposal_store_mut(&mut self) -> &mut ProposalStore {
1177 self.public_group.proposal_store_mut()
1178 }
1179
1180 pub(crate) fn context(&self) -> &GroupContext {
1182 self.public_group.group_context()
1183 }
1184
1185 pub(crate) fn version(&self) -> ProtocolVersion {
1187 self.public_group.version()
1188 }
1189
1190 #[inline]
1192 pub(crate) fn reset_aad(&mut self) {
1193 self.aad.clear();
1194 #[cfg(feature = "extensions-draft")]
1195 {
1196 self.safe_aad = SafeAad::empty();
1197 }
1198 }
1199
1200 pub fn public_group(&self) -> &PublicGroup {
1202 &self.public_group
1203 }
1204}
1205
1206#[cfg(feature = "virtual-clients-draft")]
1217#[derive(Debug, Clone)]
1218pub struct HandshakeConfirmationData {
1219 pub epoch: GroupEpoch,
1222 pub generation: u32,
1224 pub generation_id: Option<crate::components::vc_derivation_info::GenerationId>,
1229}
1230
1231pub(crate) struct HandshakeFramingOutput {
1239 pub(crate) message: MlsMessageOut,
1240 #[cfg(feature = "virtual-clients-draft")]
1241 pub(crate) confirmation: Option<HandshakeConfirmationData>,
1242}
1243
1244impl MlsGroup {
1246 #[openmls_traits::maybe_async]
1251 pub(super) async fn store_epoch_keypairs<Storage: StorageProvider>(
1252 &self,
1253 store: &Storage,
1254 keypair_references: &[EncryptionKeyPair],
1255 ) -> Result<(), Storage::Error> {
1256 store
1257 .write_encryption_epoch_key_pairs(
1258 self.group_id(),
1259 &self.context().epoch(),
1260 self.own_leaf_index().u32(),
1261 keypair_references,
1262 )
1263 .await
1264 }
1265
1266 #[openmls_traits::maybe_async]
1271 pub(super) async fn read_epoch_keypairs<Storage: StorageProvider>(
1272 &self,
1273 store: &Storage,
1274 ) -> Result<Vec<EncryptionKeyPair>, Storage::Error> {
1275 store
1276 .encryption_epoch_key_pairs(
1277 self.group_id(),
1278 &self.context().epoch(),
1279 self.own_leaf_index().u32(),
1280 )
1281 .await
1282 }
1283
1284 #[cfg(not(feature = "virtual-clients-draft"))]
1289 #[openmls_traits::maybe_async]
1290 pub(super) async fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
1291 &self,
1292 store: &Storage,
1293 ) -> Result<(), Storage::Error> {
1294 store
1295 .delete_encryption_epoch_key_pairs(
1296 self.group_id(),
1297 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
1298 self.own_leaf_index().u32(),
1299 )
1300 .await
1301 }
1302
1303 #[cfg(feature = "virtual-clients-draft")]
1304 #[openmls_traits::maybe_async]
1305 pub(super) async fn delete_previous_epoch_keypairs<Storage: StorageProvider>(
1306 &self,
1307 store: &Storage,
1308 previous_own_leaf_index: LeafNodeIndex,
1309 ) -> Result<(), Storage::Error> {
1310 store
1316 .delete_encryption_epoch_key_pairs(
1317 self.group_id(),
1318 &GroupEpoch::from(self.context().epoch().as_u64() - 1),
1319 previous_own_leaf_index.u32(),
1320 )
1321 .await
1322 }
1323
1324 #[openmls_traits::maybe_async]
1327 pub(super) async fn store<Storage: crate::storage::StorageProvider>(
1328 &self,
1329 storage: &Storage,
1330 ) -> Result<(), Storage::Error> {
1331 self.public_group.store(storage).await?;
1332 storage
1333 .write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)
1334 .await?;
1335 storage
1336 .write_own_leaf_index(self.group_id(), &self.own_leaf_index)
1337 .await?;
1338 storage
1339 .write_message_secrets(self.group_id(), &self.message_secrets_store)
1340 .await?;
1341 storage
1342 .write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)
1343 .await?;
1344 storage
1345 .write_mls_join_config(self.group_id(), &self.mls_group_config)
1346 .await?;
1347 storage
1348 .write_group_state(self.group_id(), &self.group_state)
1349 .await?;
1350 #[cfg(feature = "extensions-draft")]
1351 if let Some(application_export_tree) = &self.application_export_tree {
1352 storage
1353 .write_application_export_tree(self.group_id(), application_export_tree)
1354 .await?;
1355 }
1356
1357 Ok(())
1358 }
1359
1360 #[openmls_traits::maybe_async]
1364 async fn content_to_mls_message(
1365 &mut self,
1366 mls_auth_content: AuthenticatedContent,
1367 provider: &impl OpenMlsProvider,
1368 ) -> Result<HandshakeFramingOutput, LibraryError> {
1369 let output = match self.configuration().wire_format_policy().outgoing() {
1370 OutgoingWireFormatPolicy::AlwaysPlaintext => {
1371 let mut plaintext: PublicMessage = mls_auth_content.into();
1372 if plaintext.sender().is_member() {
1374 plaintext.set_membership_tag(
1375 provider.crypto(),
1376 self.ciphersuite(),
1377 self.message_secrets().membership_key(),
1378 self.message_secrets().serialized_context(),
1379 )?;
1380 }
1381 HandshakeFramingOutput {
1382 message: plaintext.into(),
1383 #[cfg(feature = "virtual-clients-draft")]
1384 confirmation: None,
1385 }
1386 }
1387 OutgoingWireFormatPolicy::AlwaysCiphertext => {
1388 #[cfg(feature = "virtual-clients-draft")]
1392 let epoch = self.epoch();
1393 let encryption_output = self
1394 .encrypt(mls_auth_content, provider)
1395 .await
1396 .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
1398 let message = MlsMessageOut::from_private_message(
1399 encryption_output.private_message,
1400 self.version(),
1401 );
1402 HandshakeFramingOutput {
1403 message,
1404 #[cfg(feature = "virtual-clients-draft")]
1405 confirmation: Some(HandshakeConfirmationData {
1406 epoch,
1407 generation: encryption_output.generation,
1408 generation_id: encryption_output.generation_id,
1409 }),
1410 }
1411 }
1412 };
1413 Ok(output)
1414 }
1415
1416 fn is_operational(&self) -> Result<(), MlsGroupStateError> {
1419 match self.group_state {
1420 MlsGroupState::PendingCommit(_) => Err(MlsGroupStateError::PendingCommit),
1421 MlsGroupState::Inactive => Err(MlsGroupStateError::UseAfterEviction),
1422 MlsGroupState::Operational => Ok(()),
1423 }
1424 }
1425}
1426
1427impl MlsGroup {
1429 #[cfg(any(feature = "test-utils", test))]
1430 pub fn export_group_context(&self) -> &GroupContext {
1431 self.context()
1432 }
1433
1434 #[cfg(any(feature = "test-utils", test))]
1435 pub fn tree_hash(&self) -> &[u8] {
1436 self.public_group().group_context().tree_hash()
1437 }
1438
1439 #[cfg(any(feature = "test-utils", test))]
1440 pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
1441 self.message_secrets_store.message_secrets_mut()
1442 }
1443
1444 #[cfg(any(feature = "test-utils", test))]
1445 pub fn print_ratchet_tree(&self, message: &str) {
1446 println!("{}: {}", message, self.public_group().export_ratchet_tree());
1447 }
1448
1449 #[cfg(any(feature = "test-utils", test))]
1450 pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
1451 self.public_group.context_mut()
1452 }
1453
1454 #[cfg(test)]
1455 pub(crate) fn set_own_leaf_index(&mut self, own_leaf_index: LeafNodeIndex) {
1456 self.own_leaf_index = own_leaf_index;
1457 }
1458
1459 #[cfg(test)]
1460 pub(crate) fn own_tree_position(&self) -> TreePosition {
1461 TreePosition::new(self.group_id().clone(), self.own_leaf_index())
1462 }
1463
1464 #[cfg(test)]
1465 pub(crate) fn message_secrets_store(&self) -> &MessageSecretsStore {
1466 &self.message_secrets_store
1467 }
1468
1469 #[cfg(test)]
1470 pub(crate) fn resumption_psk_store(&self) -> &ResumptionPskStore {
1471 &self.resumption_psk_store
1472 }
1473
1474 #[cfg(test)]
1475 pub(crate) fn set_group_context(&mut self, group_context: GroupContext) {
1476 self.public_group.set_group_context(group_context)
1477 }
1478
1479 #[cfg(any(test, feature = "test-utils"))]
1480 #[openmls_traits::maybe_async]
1481 pub async fn ensure_persistence(
1482 &self,
1483 storage: &impl StorageProvider,
1484 ) -> Result<(), LibraryError> {
1485 let loaded = MlsGroup::load(storage, self.group_id())
1486 .await
1487 .map_err(|_| LibraryError::custom("Failed to load group from storage"))?;
1488 let other = loaded.ok_or_else(|| LibraryError::custom("Group not found in storage"))?;
1489
1490 if self != &other {
1491 let mut diagnostics = Vec::new();
1492
1493 if self.mls_group_config != other.mls_group_config {
1494 diagnostics.push(format!(
1495 "mls_group_config:\n Current: {:?}\n Loaded: {:?}",
1496 self.mls_group_config, other.mls_group_config
1497 ));
1498 }
1499 if self.public_group != other.public_group {
1500 diagnostics.push(format!(
1501 "public_group:\n Current: {:?}\n Loaded: {:?}",
1502 self.public_group, other.public_group
1503 ));
1504 }
1505 if self.group_epoch_secrets != other.group_epoch_secrets {
1506 diagnostics.push(format!(
1507 "group_epoch_secrets:\n Current: {:?}\n Loaded: {:?}",
1508 self.group_epoch_secrets, other.group_epoch_secrets
1509 ));
1510 }
1511 if self.own_leaf_index != other.own_leaf_index {
1512 diagnostics.push(format!(
1513 "own_leaf_index:\n Current: {:?}\n Loaded: {:?}",
1514 self.own_leaf_index, other.own_leaf_index
1515 ));
1516 }
1517 if self.message_secrets_store != other.message_secrets_store {
1518 diagnostics.push(format!(
1519 "message_secrets_store:\n Current: {:?}\n Loaded: {:?}",
1520 self.message_secrets_store, other.message_secrets_store
1521 ));
1522 }
1523 if self.resumption_psk_store != other.resumption_psk_store {
1524 diagnostics.push(format!(
1525 "resumption_psk_store:\n Current: {:?}\n Loaded: {:?}",
1526 self.resumption_psk_store, other.resumption_psk_store
1527 ));
1528 }
1529 if self.own_leaf_nodes != other.own_leaf_nodes {
1530 diagnostics.push(format!(
1531 "own_leaf_nodes:\n Current: {:?}\n Loaded: {:?}",
1532 self.own_leaf_nodes, other.own_leaf_nodes
1533 ));
1534 }
1535 if self.aad != other.aad {
1536 diagnostics.push(format!(
1537 "aad:\n Current: {:?}\n Loaded: {:?}",
1538 self.aad, other.aad
1539 ));
1540 }
1541 if self.group_state != other.group_state {
1542 diagnostics.push(format!(
1543 "group_state:\n Current: {:?}\n Loaded: {:?}",
1544 self.group_state, other.group_state
1545 ));
1546 }
1547 #[cfg(feature = "extensions-draft")]
1548 if self.application_export_tree != other.application_export_tree {
1549 diagnostics.push(format!(
1550 "application_export_tree:\n Current: {:?}\n Loaded: {:?}",
1551 self.application_export_tree, other.application_export_tree
1552 ));
1553 }
1554 #[cfg(feature = "virtual-clients-draft")]
1555 if self.emulation_group != other.emulation_group {
1556 diagnostics.push(format!(
1557 "emulation_group:\n Current: {:?}\n Loaded: {:?}",
1558 self.emulation_group, other.emulation_group
1559 ));
1560 }
1561
1562 log::error!(
1563 "Loaded group does not match current group! Differing fields ({}):\n\n{}",
1564 diagnostics.len(),
1565 diagnostics.join("\n\n")
1566 );
1567
1568 return Err(LibraryError::custom(
1569 "Loaded group does not match current group",
1570 ));
1571 }
1572
1573 Ok(())
1574 }
1575}
1576
1577#[derive(Debug)]
1580pub struct StagedWelcome {
1581 mls_group_config: MlsGroupJoinConfig,
1583 public_group: PublicGroup,
1584 group_epoch_secrets: GroupEpochSecrets,
1585 own_leaf_index: LeafNodeIndex,
1586
1587 message_secrets_store: MessageSecretsStore,
1594
1595 #[cfg(feature = "extensions-draft")]
1598 application_export_secret: ApplicationExportSecret,
1599
1600 resumption_psk_store: ResumptionPskStore,
1602
1603 verifiable_group_info: VerifiableGroupInfo,
1605
1606 key_material: WelcomeKeyMaterial,
1608
1609 path_keypairs: Option<Vec<EncryptionKeyPair>>,
1611
1612 #[cfg(feature = "virtual-clients-draft")]
1615 emulation_group: bool,
1616}
1617
1618pub struct ProcessedWelcome {
1625 mls_group_config: MlsGroupJoinConfig,
1627
1628 ciphersuite: Ciphersuite,
1631 group_secrets: GroupSecrets,
1632 epoch_secrets: crate::schedule::EpochSecretsResult,
1633 verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo,
1634 resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore,
1635 key_material: WelcomeKeyMaterial,
1636}
1637
1638#[derive(Debug)]
1640pub struct WelcomeKeyMaterial {
1641 inner: WelcomeKeyMaterialInner,
1642}
1643
1644#[derive(Debug)]
1653pub(crate) enum WelcomeKeyMaterialInner {
1654 KeyPackage(Box<KeyPackageBundle>),
1657 #[cfg(feature = "virtual-clients-draft")]
1660 VirtualClient(crate::components::vc_derivation_info::VcWelcomeMaterial),
1661}
1662
1663impl WelcomeKeyMaterial {
1664 pub(crate) fn with_key_package_bundle(key_package: KeyPackageBundle) -> Self {
1666 Self {
1667 inner: WelcomeKeyMaterialInner::KeyPackage(Box::new(key_package)),
1668 }
1669 }
1670
1671 #[cfg(feature = "virtual-clients-draft")]
1675 pub(crate) fn with_vc_welcome_material(
1676 material: crate::components::vc_derivation_info::VcWelcomeMaterial,
1677 ) -> Self {
1678 Self {
1679 inner: WelcomeKeyMaterialInner::VirtualClient(material),
1680 }
1681 }
1682
1683 pub(crate) fn inner(&self) -> &WelcomeKeyMaterialInner {
1684 &self.inner
1685 }
1686
1687 pub fn key_package_ref(
1693 &self,
1694 crypto: &impl OpenMlsCrypto,
1695 ) -> Result<crate::ciphersuite::hash_ref::KeyPackageRef, LibraryError> {
1696 match &self.inner {
1697 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.key_package().hash_ref(crypto),
1698 #[cfg(feature = "virtual-clients-draft")]
1699 WelcomeKeyMaterialInner::VirtualClient(material) => {
1700 Ok(material.key_package_ref.clone())
1701 }
1702 }
1703 }
1704
1705 pub fn init_private_key(&self) -> &crate::ciphersuite::HpkePrivateKey {
1707 match &self.inner {
1708 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.init_private_key(),
1709 #[cfg(feature = "virtual-clients-draft")]
1710 WelcomeKeyMaterialInner::VirtualClient(material) => &material.init_private_key,
1711 }
1712 }
1713
1714 pub fn hpke_init_key(&self) -> &InitKey {
1716 match &self.inner {
1717 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.key_package().hpke_init_key(),
1718 #[cfg(feature = "virtual-clients-draft")]
1719 WelcomeKeyMaterialInner::VirtualClient(material) => &material.init_key,
1720 }
1721 }
1722
1723 pub fn key_package_bundle(&self) -> Option<&KeyPackageBundle> {
1727 match &self.inner {
1728 WelcomeKeyMaterialInner::KeyPackage(bundle) => Some(bundle),
1729 #[cfg(feature = "virtual-clients-draft")]
1730 WelcomeKeyMaterialInner::VirtualClient(_) => None,
1731 }
1732 }
1733
1734 #[cfg(feature = "virtual-clients-draft")]
1737 pub(crate) fn vc_welcome_material(
1738 &self,
1739 ) -> Option<&crate::components::vc_derivation_info::VcWelcomeMaterial> {
1740 match &self.inner {
1741 WelcomeKeyMaterialInner::KeyPackage(_) => None,
1742 WelcomeKeyMaterialInner::VirtualClient(material) => Some(material),
1743 }
1744 }
1745
1746 fn encryption_key_pair(&self) -> EncryptionKeyPair {
1748 match &self.inner {
1749 WelcomeKeyMaterialInner::KeyPackage(bundle) => bundle.encryption_key_pair(),
1750 #[cfg(feature = "virtual-clients-draft")]
1751 WelcomeKeyMaterialInner::VirtualClient(material) => material.encryption_keypair.clone(),
1752 }
1753 }
1754}