• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 // Copyright by contributors to this project.
3 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
4 
5 use alloc::vec;
6 use alloc::vec::Vec;
7 use core::fmt::{self, Debug};
8 use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
9 use mls_rs_core::error::IntoAnyError;
10 use mls_rs_core::secret::Secret;
11 use mls_rs_core::time::MlsTime;
12 
13 use crate::cipher_suite::CipherSuite;
14 use crate::client::MlsError;
15 use crate::client_config::ClientConfig;
16 use crate::crypto::{HpkeCiphertext, SignatureSecretKey};
17 use crate::extension::RatchetTreeExt;
18 use crate::identity::SigningIdentity;
19 use crate::key_package::{KeyPackage, KeyPackageRef};
20 use crate::protocol_version::ProtocolVersion;
21 use crate::psk::secret::PskSecret;
22 use crate::psk::PreSharedKeyID;
23 use crate::signer::Signable;
24 use crate::tree_kem::hpke_encryption::HpkeEncryptable;
25 use crate::tree_kem::kem::TreeKem;
26 use crate::tree_kem::node::LeafIndex;
27 use crate::tree_kem::path_secret::PathSecret;
28 pub use crate::tree_kem::Capabilities;
29 use crate::tree_kem::{
30     leaf_node::LeafNode,
31     leaf_node_validator::{LeafNodeValidator, ValidationContext},
32 };
33 use crate::tree_kem::{math as tree_math, ValidatedUpdatePath};
34 use crate::tree_kem::{TreeKemPrivate, TreeKemPublic};
35 use crate::{CipherSuiteProvider, CryptoProvider};
36 
37 #[cfg(feature = "by_ref_proposal")]
38 use crate::crypto::{HpkePublicKey, HpkeSecretKey};
39 
40 use crate::extension::ExternalPubExt;
41 
42 use self::message_hash::MessageHash;
43 #[cfg(feature = "private_message")]
44 use self::mls_rules::{EncryptionOptions, MlsRules};
45 
46 #[cfg(feature = "psk")]
47 pub use self::resumption::ReinitClient;
48 
49 #[cfg(feature = "psk")]
50 use crate::psk::{
51     resolver::PskResolver, secret::PskSecretInput, ExternalPskId, JustPreSharedKeyID, PskGroupId,
52     ResumptionPSKUsage, ResumptionPsk,
53 };
54 
55 #[cfg(feature = "private_message")]
56 use ciphertext_processor::*;
57 
58 use confirmation_tag::*;
59 use framing::*;
60 use key_schedule::*;
61 use membership_tag::*;
62 use message_signature::*;
63 use message_verifier::*;
64 use proposal::*;
65 #[cfg(feature = "by_ref_proposal")]
66 use proposal_cache::*;
67 use state::*;
68 use transcript_hash::*;
69 
70 #[cfg(test)]
71 pub(crate) use self::commit::test_utils::CommitModifiers;
72 
73 #[cfg(all(test, feature = "private_message"))]
74 pub use self::framing::PrivateMessage;
75 
76 #[cfg(feature = "psk")]
77 use self::proposal_filter::ProposalInfo;
78 
79 #[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
80 use secret_tree::*;
81 
82 #[cfg(feature = "prior_epoch")]
83 use self::epoch::PriorEpoch;
84 
85 use self::epoch::EpochSecrets;
86 pub use self::message_processor::{
87     ApplicationMessageDescription, CommitMessageDescription, ProposalMessageDescription,
88     ProposalSender, ReceivedMessage, StateUpdate,
89 };
90 use self::message_processor::{EventOrContent, MessageProcessor, ProvisionalState};
91 #[cfg(feature = "by_ref_proposal")]
92 use self::proposal_ref::ProposalRef;
93 use self::state_repo::GroupStateRepository;
94 pub use group_info::GroupInfo;
95 
96 pub use self::framing::{ContentType, Sender};
97 pub use commit::*;
98 pub use context::GroupContext;
99 pub use roster::*;
100 
101 pub(crate) use transcript_hash::ConfirmedTranscriptHash;
102 pub(crate) use util::*;
103 
104 #[cfg(all(feature = "by_ref_proposal", feature = "external_client"))]
105 pub use self::message_processor::CachedProposal;
106 
107 #[cfg(feature = "private_message")]
108 mod ciphertext_processor;
109 
110 mod commit;
111 pub(crate) mod confirmation_tag;
112 mod context;
113 pub(crate) mod epoch;
114 pub(crate) mod framing;
115 mod group_info;
116 pub(crate) mod key_schedule;
117 mod membership_tag;
118 pub(crate) mod message_hash;
119 pub(crate) mod message_processor;
120 pub(crate) mod message_signature;
121 pub(crate) mod message_verifier;
122 pub mod mls_rules;
123 #[cfg(feature = "private_message")]
124 pub(crate) mod padding;
125 /// Proposals to evolve a MLS [`Group`]
126 pub mod proposal;
127 mod proposal_cache;
128 pub(crate) mod proposal_filter;
129 #[cfg(feature = "by_ref_proposal")]
130 pub(crate) mod proposal_ref;
131 #[cfg(feature = "psk")]
132 mod resumption;
133 mod roster;
134 pub(crate) mod snapshot;
135 pub(crate) mod state;
136 
137 #[cfg(feature = "prior_epoch")]
138 pub(crate) mod state_repo;
139 #[cfg(not(feature = "prior_epoch"))]
140 pub(crate) mod state_repo_light;
141 #[cfg(not(feature = "prior_epoch"))]
142 pub(crate) use state_repo_light as state_repo;
143 
144 pub(crate) mod transcript_hash;
145 mod util;
146 
147 /// External commit building.
148 pub mod external_commit;
149 
150 #[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
151 pub(crate) mod secret_tree;
152 
153 #[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
154 pub use secret_tree::MessageKeyData as MessageKey;
155 
156 #[cfg(all(test, feature = "rfc_compliant"))]
157 mod interop_test_vectors;
158 
159 mod exported_tree;
160 
161 pub use exported_tree::ExportedTree;
162 
163 #[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
164 struct GroupSecrets {
165     joiner_secret: JoinerSecret,
166     path_secret: Option<PathSecret>,
167     psks: Vec<PreSharedKeyID>,
168 }
169 
170 impl HpkeEncryptable for GroupSecrets {
171     const ENCRYPT_LABEL: &'static str = "Welcome";
172 
from_bytes(bytes: Vec<u8>) -> Result<Self, MlsError>173     fn from_bytes(bytes: Vec<u8>) -> Result<Self, MlsError> {
174         Self::mls_decode(&mut bytes.as_slice()).map_err(Into::into)
175     }
176 
get_bytes(&self) -> Result<Vec<u8>, MlsError>177     fn get_bytes(&self) -> Result<Vec<u8>, MlsError> {
178         self.mls_encode_to_vec().map_err(Into::into)
179     }
180 }
181 
182 #[derive(Clone, Debug, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
183 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
184 pub(crate) struct EncryptedGroupSecrets {
185     pub new_member: KeyPackageRef,
186     pub encrypted_group_secrets: HpkeCiphertext,
187 }
188 
189 #[derive(Clone, Eq, PartialEq, MlsSize, MlsEncode, MlsDecode)]
190 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
191 pub(crate) struct Welcome {
192     pub cipher_suite: CipherSuite,
193     pub secrets: Vec<EncryptedGroupSecrets>,
194     #[mls_codec(with = "mls_rs_codec::byte_vec")]
195     pub encrypted_group_info: Vec<u8>,
196 }
197 
198 impl Debug for Welcome {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result199     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200         f.debug_struct("Welcome")
201             .field("cipher_suite", &self.cipher_suite)
202             .field("secrets", &self.secrets)
203             .field(
204                 "encrypted_group_info",
205                 &mls_rs_core::debug::pretty_bytes(&self.encrypted_group_info),
206             )
207             .finish()
208     }
209 }
210 
211 #[derive(Clone, Debug)]
212 #[cfg_attr(
213     all(feature = "ffi", not(test)),
214     safer_ffi_gen::ffi_type(clone, opaque)
215 )]
216 #[non_exhaustive]
217 /// Information provided to new members upon joining a group.
218 pub struct NewMemberInfo {
219     /// Group info extensions found within the Welcome message used to join
220     /// the group.
221     pub group_info_extensions: ExtensionList,
222 }
223 
224 #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen)]
225 impl NewMemberInfo {
new(group_info_extensions: ExtensionList) -> Self226     pub(crate) fn new(group_info_extensions: ExtensionList) -> Self {
227         let mut new_member_info = Self {
228             group_info_extensions,
229         };
230 
231         new_member_info.ungrease();
232 
233         new_member_info
234     }
235 
236     /// Group info extensions found within the Welcome message used to join
237     /// the group.
238     #[cfg(feature = "ffi")]
group_info_extensions(&self) -> &ExtensionList239     pub fn group_info_extensions(&self) -> &ExtensionList {
240         &self.group_info_extensions
241     }
242 }
243 
244 /// An MLS end-to-end encrypted group.
245 ///
246 /// # Group Evolution
247 ///
248 /// MLS Groups are evolved via a propose-then-commit system. Each group state
249 /// produced by a commit is called an epoch and can produce and consume
250 /// application, proposal, and commit messages. A [commit](Group::commit) is used
251 /// to advance to the next epoch by applying existing proposals sent in
252 /// the current epoch by-reference along with an optional set of proposals
253 /// that are included by-value using a [`CommitBuilder`].
254 #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::ffi_type(opaque))]
255 #[derive(Clone)]
256 pub struct Group<C>
257 where
258     C: ClientConfig,
259 {
260     config: C,
261     cipher_suite_provider: <C::CryptoProvider as CryptoProvider>::CipherSuiteProvider,
262     state_repo: GroupStateRepository<C::GroupStateStorage, C::KeyPackageRepository>,
263     pub(crate) state: GroupState,
264     epoch_secrets: EpochSecrets,
265     private_tree: TreeKemPrivate,
266     key_schedule: KeySchedule,
267     #[cfg(feature = "by_ref_proposal")]
268     pending_updates:
269         crate::map::SmallMap<HpkePublicKey, (HpkeSecretKey, Option<SignatureSecretKey>)>, // Hash of leaf node hpke public key to secret key
270     pending_commit: Option<CommitGeneration>,
271     #[cfg(feature = "psk")]
272     previous_psk: Option<PskSecretInput>,
273     #[cfg(test)]
274     pub(crate) commit_modifiers: CommitModifiers,
275     pub(crate) signer: SignatureSecretKey,
276 }
277 
278 #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen)]
279 impl<C> Group<C>
280 where
281     C: ClientConfig + Clone,
282 {
283     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
new( config: C, group_id: Option<Vec<u8>>, cipher_suite: CipherSuite, protocol_version: ProtocolVersion, signing_identity: SigningIdentity, group_context_extensions: ExtensionList, signer: SignatureSecretKey, ) -> Result<Self, MlsError>284     pub(crate) async fn new(
285         config: C,
286         group_id: Option<Vec<u8>>,
287         cipher_suite: CipherSuite,
288         protocol_version: ProtocolVersion,
289         signing_identity: SigningIdentity,
290         group_context_extensions: ExtensionList,
291         signer: SignatureSecretKey,
292     ) -> Result<Self, MlsError> {
293         let cipher_suite_provider = cipher_suite_provider(config.crypto_provider(), cipher_suite)?;
294 
295         let (leaf_node, leaf_node_secret) = LeafNode::generate(
296             &cipher_suite_provider,
297             config.leaf_properties(),
298             signing_identity,
299             &signer,
300             config.lifetime(),
301         )
302         .await?;
303 
304         let identity_provider = config.identity_provider();
305 
306         let leaf_node_validator = LeafNodeValidator::new(
307             &cipher_suite_provider,
308             &identity_provider,
309             Some(&group_context_extensions),
310         );
311 
312         leaf_node_validator
313             .check_if_valid(&leaf_node, ValidationContext::Add(None))
314             .await?;
315 
316         let (mut public_tree, private_tree) = TreeKemPublic::derive(
317             leaf_node,
318             leaf_node_secret,
319             &config.identity_provider(),
320             &group_context_extensions,
321         )
322         .await?;
323 
324         let tree_hash = public_tree.tree_hash(&cipher_suite_provider).await?;
325 
326         let group_id = group_id.map(Ok).unwrap_or_else(|| {
327             cipher_suite_provider
328                 .random_bytes_vec(cipher_suite_provider.kdf_extract_size())
329                 .map_err(|e| MlsError::CryptoProviderError(e.into_any_error()))
330         })?;
331 
332         let context = GroupContext::new_group(
333             protocol_version,
334             cipher_suite,
335             group_id,
336             tree_hash,
337             group_context_extensions,
338         );
339 
340         let state_repo = GroupStateRepository::new(
341             #[cfg(feature = "prior_epoch")]
342             context.group_id.clone(),
343             config.group_state_storage(),
344             config.key_package_repo(),
345             None,
346         )?;
347 
348         let key_schedule_result = KeySchedule::from_random_epoch_secret(
349             &cipher_suite_provider,
350             #[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
351             public_tree.total_leaf_count(),
352         )
353         .await?;
354 
355         let confirmation_tag = ConfirmationTag::create(
356             &key_schedule_result.confirmation_key,
357             &vec![].into(),
358             &cipher_suite_provider,
359         )
360         .await?;
361 
362         let interim_hash = InterimTranscriptHash::create(
363             &cipher_suite_provider,
364             &vec![].into(),
365             &confirmation_tag,
366         )
367         .await?;
368 
369         Ok(Self {
370             config,
371             state: GroupState::new(context, public_tree, interim_hash, confirmation_tag),
372             private_tree,
373             key_schedule: key_schedule_result.key_schedule,
374             #[cfg(feature = "by_ref_proposal")]
375             pending_updates: Default::default(),
376             pending_commit: None,
377             #[cfg(test)]
378             commit_modifiers: Default::default(),
379             epoch_secrets: key_schedule_result.epoch_secrets,
380             state_repo,
381             cipher_suite_provider,
382             #[cfg(feature = "psk")]
383             previous_psk: None,
384             signer,
385         })
386     }
387 
388     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
join( welcome: &MlsMessage, tree_data: Option<ExportedTree<'_>>, config: C, signer: SignatureSecretKey, ) -> Result<(Self, NewMemberInfo), MlsError>389     pub(crate) async fn join(
390         welcome: &MlsMessage,
391         tree_data: Option<ExportedTree<'_>>,
392         config: C,
393         signer: SignatureSecretKey,
394     ) -> Result<(Self, NewMemberInfo), MlsError> {
395         Self::from_welcome_message(
396             welcome,
397             tree_data,
398             config,
399             signer,
400             #[cfg(feature = "psk")]
401             None,
402         )
403         .await
404     }
405 
406     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
from_welcome_message( welcome: &MlsMessage, tree_data: Option<ExportedTree<'_>>, config: C, signer: SignatureSecretKey, #[cfg(feature = "psk")] additional_psk: Option<PskSecretInput>, ) -> Result<(Self, NewMemberInfo), MlsError>407     async fn from_welcome_message(
408         welcome: &MlsMessage,
409         tree_data: Option<ExportedTree<'_>>,
410         config: C,
411         signer: SignatureSecretKey,
412         #[cfg(feature = "psk")] additional_psk: Option<PskSecretInput>,
413     ) -> Result<(Self, NewMemberInfo), MlsError> {
414         let protocol_version = welcome.version;
415 
416         if !config.version_supported(protocol_version) {
417             return Err(MlsError::UnsupportedProtocolVersion(protocol_version));
418         }
419 
420         let MlsMessagePayload::Welcome(welcome) = &welcome.payload else {
421             return Err(MlsError::UnexpectedMessageType);
422         };
423 
424         let cipher_suite_provider =
425             cipher_suite_provider(config.crypto_provider(), welcome.cipher_suite)?;
426 
427         let (encrypted_group_secrets, key_package_generation) =
428             find_key_package_generation(&config.key_package_repo(), &welcome.secrets).await?;
429 
430         let key_package_version = key_package_generation.key_package.version;
431 
432         if key_package_version != protocol_version {
433             return Err(MlsError::ProtocolVersionMismatch);
434         }
435 
436         // Decrypt the encrypted_group_secrets using HPKE with the algorithms indicated by the
437         // cipher suite and the HPKE private key corresponding to the GroupSecrets. If a
438         // PreSharedKeyID is part of the GroupSecrets and the client is not in possession of
439         // the corresponding PSK, return an error
440         let group_secrets = GroupSecrets::decrypt(
441             &cipher_suite_provider,
442             &key_package_generation.init_secret_key,
443             &key_package_generation.key_package.hpke_init_key,
444             &welcome.encrypted_group_info,
445             &encrypted_group_secrets.encrypted_group_secrets,
446         )
447         .await?;
448 
449         #[cfg(feature = "psk")]
450         let psk_secret = if let Some(psk) = additional_psk {
451             let psk_id = group_secrets
452                 .psks
453                 .first()
454                 .ok_or(MlsError::UnexpectedPskId)?;
455 
456             match &psk_id.key_id {
457                 JustPreSharedKeyID::Resumption(r) if r.usage != ResumptionPSKUsage::Application => {
458                     Ok(())
459                 }
460                 _ => Err(MlsError::UnexpectedPskId),
461             }?;
462 
463             let mut psk = psk;
464             psk.id.psk_nonce = psk_id.psk_nonce.clone();
465             PskSecret::calculate(&[psk], &cipher_suite_provider).await?
466         } else {
467             PskResolver::<
468                 <C as ClientConfig>::GroupStateStorage,
469                 <C as ClientConfig>::KeyPackageRepository,
470                 <C as ClientConfig>::PskStore,
471             > {
472                 group_context: None,
473                 current_epoch: None,
474                 prior_epochs: None,
475                 psk_store: &config.secret_store(),
476             }
477             .resolve_to_secret(&group_secrets.psks, &cipher_suite_provider)
478             .await?
479         };
480 
481         #[cfg(not(feature = "psk"))]
482         let psk_secret = PskSecret::new(&cipher_suite_provider);
483 
484         // From the joiner_secret in the decrypted GroupSecrets object and the PSKs specified in
485         // the GroupSecrets, derive the welcome_secret and using that the welcome_key and
486         // welcome_nonce.
487         let welcome_secret = WelcomeSecret::from_joiner_secret(
488             &cipher_suite_provider,
489             &group_secrets.joiner_secret,
490             &psk_secret,
491         )
492         .await?;
493 
494         // Use the key and nonce to decrypt the encrypted_group_info field.
495         let decrypted_group_info = welcome_secret
496             .decrypt(&welcome.encrypted_group_info)
497             .await?;
498 
499         let group_info = GroupInfo::mls_decode(&mut &**decrypted_group_info)?;
500 
501         let public_tree = validate_group_info_joiner(
502             protocol_version,
503             &group_info,
504             tree_data,
505             &config.identity_provider(),
506             &cipher_suite_provider,
507         )
508         .await?;
509 
510         // Identify a leaf in the tree array (any even-numbered node) whose leaf_node is identical
511         // to the leaf_node field of the KeyPackage. If no such field exists, return an error. Let
512         // index represent the index of this node among the leaves in the tree, namely the index of
513         // the node in the tree array divided by two.
514         let self_index = public_tree
515             .find_leaf_node(&key_package_generation.key_package.leaf_node)
516             .ok_or(MlsError::WelcomeKeyPackageNotFound)?;
517 
518         let used_key_package_ref = key_package_generation.reference;
519 
520         let mut private_tree =
521             TreeKemPrivate::new_self_leaf(self_index, key_package_generation.leaf_node_secret_key);
522 
523         // If the path_secret value is set in the GroupSecrets object
524         if let Some(path_secret) = group_secrets.path_secret {
525             private_tree
526                 .update_secrets(
527                     &cipher_suite_provider,
528                     group_info.signer,
529                     path_secret,
530                     &public_tree,
531                 )
532                 .await?;
533         }
534 
535         // Use the joiner_secret from the GroupSecrets object to generate the epoch secret and
536         // other derived secrets for the current epoch.
537         let key_schedule_result = KeySchedule::from_joiner(
538             &cipher_suite_provider,
539             &group_secrets.joiner_secret,
540             &group_info.group_context,
541             #[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
542             public_tree.total_leaf_count(),
543             &psk_secret,
544         )
545         .await?;
546 
547         // Verify the confirmation tag in the GroupInfo using the derived confirmation key and the
548         // confirmed_transcript_hash from the GroupInfo.
549         if !group_info
550             .confirmation_tag
551             .matches(
552                 &key_schedule_result.confirmation_key,
553                 &group_info.group_context.confirmed_transcript_hash,
554                 &cipher_suite_provider,
555             )
556             .await?
557         {
558             return Err(MlsError::InvalidConfirmationTag);
559         }
560 
561         Self::join_with(
562             config,
563             group_info,
564             public_tree,
565             key_schedule_result.key_schedule,
566             key_schedule_result.epoch_secrets,
567             private_tree,
568             Some(used_key_package_ref),
569             signer,
570         )
571         .await
572     }
573 
574     #[allow(clippy::too_many_arguments)]
575     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
join_with( config: C, group_info: GroupInfo, public_tree: TreeKemPublic, key_schedule: KeySchedule, epoch_secrets: EpochSecrets, private_tree: TreeKemPrivate, used_key_package_ref: Option<KeyPackageRef>, signer: SignatureSecretKey, ) -> Result<(Self, NewMemberInfo), MlsError>576     async fn join_with(
577         config: C,
578         group_info: GroupInfo,
579         public_tree: TreeKemPublic,
580         key_schedule: KeySchedule,
581         epoch_secrets: EpochSecrets,
582         private_tree: TreeKemPrivate,
583         used_key_package_ref: Option<KeyPackageRef>,
584         signer: SignatureSecretKey,
585     ) -> Result<(Self, NewMemberInfo), MlsError> {
586         let cs = group_info.group_context.cipher_suite;
587 
588         let cs = config
589             .crypto_provider()
590             .cipher_suite_provider(cs)
591             .ok_or(MlsError::UnsupportedCipherSuite(cs))?;
592 
593         // Use the confirmed transcript hash and confirmation tag to compute the interim transcript
594         // hash in the new state.
595         let interim_transcript_hash = InterimTranscriptHash::create(
596             &cs,
597             &group_info.group_context.confirmed_transcript_hash,
598             &group_info.confirmation_tag,
599         )
600         .await?;
601 
602         let state_repo = GroupStateRepository::new(
603             #[cfg(feature = "prior_epoch")]
604             group_info.group_context.group_id.clone(),
605             config.group_state_storage(),
606             config.key_package_repo(),
607             used_key_package_ref,
608         )?;
609 
610         let group = Group {
611             config,
612             state: GroupState::new(
613                 group_info.group_context,
614                 public_tree,
615                 interim_transcript_hash,
616                 group_info.confirmation_tag,
617             ),
618             private_tree,
619             key_schedule,
620             #[cfg(feature = "by_ref_proposal")]
621             pending_updates: Default::default(),
622             pending_commit: None,
623             #[cfg(test)]
624             commit_modifiers: Default::default(),
625             epoch_secrets,
626             state_repo,
627             cipher_suite_provider: cs,
628             #[cfg(feature = "psk")]
629             previous_psk: None,
630             signer,
631         };
632 
633         Ok((group, NewMemberInfo::new(group_info.extensions)))
634     }
635 
636     #[inline(always)]
current_epoch_tree(&self) -> &TreeKemPublic637     pub(crate) fn current_epoch_tree(&self) -> &TreeKemPublic {
638         &self.state.public_tree
639     }
640 
641     /// The current epoch of the group. This value is incremented each
642     /// time a [`Group::commit`] message is processed.
643     #[inline(always)]
current_epoch(&self) -> u64644     pub fn current_epoch(&self) -> u64 {
645         self.context().epoch
646     }
647 
648     /// Index within the group's state for the local group instance.
649     ///
650     /// This index corresponds to indexes in content descriptions within
651     /// [`ReceivedMessage`].
652     #[inline(always)]
current_member_index(&self) -> u32653     pub fn current_member_index(&self) -> u32 {
654         self.private_tree.self_index.0
655     }
656 
current_user_leaf_node(&self) -> Result<&LeafNode, MlsError>657     fn current_user_leaf_node(&self) -> Result<&LeafNode, MlsError> {
658         self.current_epoch_tree()
659             .get_leaf_node(self.private_tree.self_index)
660     }
661 
662     /// Signing identity currently in use by the local group instance.
663     #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen_ignore)]
current_member_signing_identity(&self) -> Result<&SigningIdentity, MlsError>664     pub fn current_member_signing_identity(&self) -> Result<&SigningIdentity, MlsError> {
665         self.current_user_leaf_node().map(|ln| &ln.signing_identity)
666     }
667 
668     /// Member at a specific index in the group state.
669     ///
670     /// These indexes correspond to indexes in content descriptions within
671     /// [`ReceivedMessage`].
member_at_index(&self, index: u32) -> Option<Member>672     pub fn member_at_index(&self, index: u32) -> Option<Member> {
673         let leaf_index = LeafIndex(index);
674 
675         self.current_epoch_tree()
676             .get_leaf_node(leaf_index)
677             .ok()
678             .map(|ln| member_from_leaf_node(ln, leaf_index))
679     }
680 
681     #[cfg(feature = "by_ref_proposal")]
682     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
proposal_message( &mut self, proposal: Proposal, authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>683     async fn proposal_message(
684         &mut self,
685         proposal: Proposal,
686         authenticated_data: Vec<u8>,
687     ) -> Result<MlsMessage, MlsError> {
688         let sender = Sender::Member(*self.private_tree.self_index);
689 
690         let auth_content = AuthenticatedContent::new_signed(
691             &self.cipher_suite_provider,
692             self.context(),
693             sender,
694             Content::Proposal(alloc::boxed::Box::new(proposal.clone())),
695             &self.signer,
696             #[cfg(feature = "private_message")]
697             self.encryption_options()?.control_wire_format(sender),
698             #[cfg(not(feature = "private_message"))]
699             WireFormat::PublicMessage,
700             authenticated_data,
701         )
702         .await?;
703 
704         let sender = auth_content.content.sender;
705 
706         let proposal_desc =
707             ProposalMessageDescription::new(&self.cipher_suite_provider, &auth_content, proposal)
708                 .await?;
709 
710         let message = self.format_for_wire(auth_content).await?;
711 
712         self.state
713             .proposals
714             .insert_own(proposal_desc, &message, sender, &self.cipher_suite_provider)
715             .await?;
716 
717         Ok(message)
718     }
719 
720     /// Unique identifier for this group.
group_id(&self) -> &[u8]721     pub fn group_id(&self) -> &[u8] {
722         &self.context().group_id
723     }
724 
provisional_private_tree( &self, provisional_state: &ProvisionalState, ) -> Result<(TreeKemPrivate, Option<SignatureSecretKey>), MlsError>725     fn provisional_private_tree(
726         &self,
727         provisional_state: &ProvisionalState,
728     ) -> Result<(TreeKemPrivate, Option<SignatureSecretKey>), MlsError> {
729         let mut provisional_private_tree = self.private_tree.clone();
730         let self_index = provisional_private_tree.self_index;
731 
732         // Remove secret keys for blanked nodes
733         let path = provisional_state
734             .public_tree
735             .nodes
736             .direct_copath(self_index);
737 
738         provisional_private_tree
739             .secret_keys
740             .resize(path.len() + 1, None);
741 
742         for (i, n) in path.iter().enumerate() {
743             if provisional_state.public_tree.nodes.is_blank(n.path)? {
744                 provisional_private_tree.secret_keys[i + 1] = None;
745             }
746         }
747 
748         // Apply own update
749         let new_signer = None;
750 
751         #[cfg(feature = "by_ref_proposal")]
752         let mut new_signer = new_signer;
753 
754         #[cfg(feature = "by_ref_proposal")]
755         for p in &provisional_state.applied_proposals.updates {
756             if p.sender == Sender::Member(*self_index) {
757                 let leaf_pk = &p.proposal.leaf_node.public_key;
758 
759                 // Update the leaf in the private tree if this is our update
760                 #[cfg(feature = "std")]
761                 let new_leaf_sk_and_signer = self.pending_updates.get(leaf_pk);
762 
763                 #[cfg(not(feature = "std"))]
764                 let new_leaf_sk_and_signer = self
765                     .pending_updates
766                     .iter()
767                     .find_map(|(pk, sk)| (pk == leaf_pk).then_some(sk));
768 
769                 let new_leaf_sk = new_leaf_sk_and_signer.map(|(sk, _)| sk.clone());
770                 new_signer = new_leaf_sk_and_signer.and_then(|(_, sk)| sk.clone());
771 
772                 provisional_private_tree
773                     .update_leaf(new_leaf_sk.ok_or(MlsError::UpdateErrorNoSecretKey)?);
774 
775                 break;
776             }
777         }
778 
779         Ok((provisional_private_tree, new_signer))
780     }
781 
782     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
encrypt_group_secrets( &self, key_package: &KeyPackage, leaf_index: LeafIndex, joiner_secret: &JoinerSecret, path_secrets: Option<&Vec<Option<PathSecret>>>, #[cfg(feature = "psk")] psks: Vec<PreSharedKeyID>, encrypted_group_info: &[u8], ) -> Result<EncryptedGroupSecrets, MlsError>783     async fn encrypt_group_secrets(
784         &self,
785         key_package: &KeyPackage,
786         leaf_index: LeafIndex,
787         joiner_secret: &JoinerSecret,
788         path_secrets: Option<&Vec<Option<PathSecret>>>,
789         #[cfg(feature = "psk")] psks: Vec<PreSharedKeyID>,
790         encrypted_group_info: &[u8],
791     ) -> Result<EncryptedGroupSecrets, MlsError> {
792         let path_secret = path_secrets
793             .map(|secrets| {
794                 secrets
795                     .get(
796                         tree_math::leaf_lca_level(*self.private_tree.self_index, *leaf_index)
797                             as usize
798                             - 1,
799                     )
800                     .cloned()
801                     .flatten()
802                     .ok_or(MlsError::InvalidTreeKemPrivateKey)
803             })
804             .transpose()?;
805 
806         #[cfg(not(feature = "psk"))]
807         let psks = Vec::new();
808 
809         let group_secrets = GroupSecrets {
810             joiner_secret: joiner_secret.clone(),
811             path_secret,
812             psks,
813         };
814 
815         let encrypted_group_secrets = group_secrets
816             .encrypt(
817                 &self.cipher_suite_provider,
818                 &key_package.hpke_init_key,
819                 encrypted_group_info,
820             )
821             .await?;
822 
823         Ok(EncryptedGroupSecrets {
824             new_member: key_package
825                 .to_reference(&self.cipher_suite_provider)
826                 .await?,
827             encrypted_group_secrets,
828         })
829     }
830 
831     /// Create a proposal message that adds a new member to the group.
832     ///
833     /// `authenticated_data` will be sent unencrypted along with the contents
834     /// of the proposal message.
835     #[cfg(feature = "by_ref_proposal")]
836     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
propose_add( &mut self, key_package: MlsMessage, authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>837     pub async fn propose_add(
838         &mut self,
839         key_package: MlsMessage,
840         authenticated_data: Vec<u8>,
841     ) -> Result<MlsMessage, MlsError> {
842         let proposal = self.add_proposal(key_package)?;
843         self.proposal_message(proposal, authenticated_data).await
844     }
845 
add_proposal(&self, key_package: MlsMessage) -> Result<Proposal, MlsError>846     fn add_proposal(&self, key_package: MlsMessage) -> Result<Proposal, MlsError> {
847         Ok(Proposal::Add(alloc::boxed::Box::new(AddProposal {
848             key_package: key_package
849                 .into_key_package()
850                 .ok_or(MlsError::UnexpectedMessageType)?,
851         })))
852     }
853 
854     /// Create a proposal message that updates your own public keys.
855     ///
856     /// This proposal is useful for contributing additional forward secrecy
857     /// and post-compromise security to the group without having to perform
858     /// the necessary computation of a [`Group::commit`].
859     ///
860     /// `authenticated_data` will be sent unencrypted along with the contents
861     /// of the proposal message.
862     #[cfg(feature = "by_ref_proposal")]
863     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
propose_update( &mut self, authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>864     pub async fn propose_update(
865         &mut self,
866         authenticated_data: Vec<u8>,
867     ) -> Result<MlsMessage, MlsError> {
868         let proposal = self.update_proposal(None, None).await?;
869         self.proposal_message(proposal, authenticated_data).await
870     }
871 
872     /// Create a proposal message that updates your own public keys
873     /// as well as your credential.
874     ///
875     /// This proposal is useful for contributing additional forward secrecy
876     /// and post-compromise security to the group without having to perform
877     /// the necessary computation of a [`Group::commit`].
878     ///
879     /// Identity updates are allowed by the group by default assuming that the
880     /// new identity provided is considered
881     /// [valid](crate::IdentityProvider::validate_member)
882     /// by and matches the output of the
883     /// [identity](crate::IdentityProvider)
884     /// function of the current
885     /// [`IdentityProvider`](crate::IdentityProvider).
886     ///
887     /// `authenticated_data` will be sent unencrypted along with the contents
888     /// of the proposal message.
889     #[cfg(feature = "by_ref_proposal")]
890     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
propose_update_with_identity( &mut self, signer: SignatureSecretKey, signing_identity: SigningIdentity, authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>891     pub async fn propose_update_with_identity(
892         &mut self,
893         signer: SignatureSecretKey,
894         signing_identity: SigningIdentity,
895         authenticated_data: Vec<u8>,
896     ) -> Result<MlsMessage, MlsError> {
897         let proposal = self
898             .update_proposal(Some(signer), Some(signing_identity))
899             .await?;
900 
901         self.proposal_message(proposal, authenticated_data).await
902     }
903 
904     #[cfg(feature = "by_ref_proposal")]
905     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
update_proposal( &mut self, signer: Option<SignatureSecretKey>, signing_identity: Option<SigningIdentity>, ) -> Result<Proposal, MlsError>906     async fn update_proposal(
907         &mut self,
908         signer: Option<SignatureSecretKey>,
909         signing_identity: Option<SigningIdentity>,
910     ) -> Result<Proposal, MlsError> {
911         // Grab a copy of the current node and update it to have new key material
912         let mut new_leaf_node = self.current_user_leaf_node()?.clone();
913 
914         let secret_key = new_leaf_node
915             .update(
916                 &self.cipher_suite_provider,
917                 self.group_id(),
918                 self.current_member_index(),
919                 self.config.leaf_properties(),
920                 signing_identity,
921                 signer.as_ref().unwrap_or(&self.signer),
922             )
923             .await?;
924 
925         // Store the secret key in the pending updates storage for later
926         #[cfg(feature = "std")]
927         self.pending_updates
928             .insert(new_leaf_node.public_key.clone(), (secret_key, signer));
929 
930         #[cfg(not(feature = "std"))]
931         self.pending_updates
932             .push((new_leaf_node.public_key.clone(), (secret_key, signer)));
933 
934         Ok(Proposal::Update(UpdateProposal {
935             leaf_node: new_leaf_node,
936         }))
937     }
938 
939     /// Create a proposal message that removes an existing member from the
940     /// group.
941     ///
942     /// `authenticated_data` will be sent unencrypted along with the contents
943     /// of the proposal message.
944     #[cfg(feature = "by_ref_proposal")]
945     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
propose_remove( &mut self, index: u32, authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>946     pub async fn propose_remove(
947         &mut self,
948         index: u32,
949         authenticated_data: Vec<u8>,
950     ) -> Result<MlsMessage, MlsError> {
951         let proposal = self.remove_proposal(index)?;
952         self.proposal_message(proposal, authenticated_data).await
953     }
954 
remove_proposal(&self, index: u32) -> Result<Proposal, MlsError>955     fn remove_proposal(&self, index: u32) -> Result<Proposal, MlsError> {
956         let leaf_index = LeafIndex(index);
957 
958         // Verify that this leaf is actually in the tree
959         self.current_epoch_tree().get_leaf_node(leaf_index)?;
960 
961         Ok(Proposal::Remove(RemoveProposal {
962             to_remove: leaf_index,
963         }))
964     }
965 
966     /// Create a proposal message that adds an external pre shared key to the group.
967     ///
968     /// Each group member will need to have the PSK associated with
969     /// [`ExternalPskId`](mls_rs_core::psk::ExternalPskId) installed within
970     /// the [`PreSharedKeyStorage`](mls_rs_core::psk::PreSharedKeyStorage)
971     /// in use by this group upon processing a [commit](Group::commit) that
972     /// contains this proposal.
973     ///
974     /// `authenticated_data` will be sent unencrypted along with the contents
975     /// of the proposal message.
976     #[cfg(all(feature = "by_ref_proposal", feature = "psk"))]
977     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
propose_external_psk( &mut self, psk: ExternalPskId, authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>978     pub async fn propose_external_psk(
979         &mut self,
980         psk: ExternalPskId,
981         authenticated_data: Vec<u8>,
982     ) -> Result<MlsMessage, MlsError> {
983         let proposal = self.psk_proposal(JustPreSharedKeyID::External(psk))?;
984         self.proposal_message(proposal, authenticated_data).await
985     }
986 
987     #[cfg(feature = "psk")]
psk_proposal(&self, key_id: JustPreSharedKeyID) -> Result<Proposal, MlsError>988     fn psk_proposal(&self, key_id: JustPreSharedKeyID) -> Result<Proposal, MlsError> {
989         Ok(Proposal::Psk(PreSharedKeyProposal {
990             psk: PreSharedKeyID::new(key_id, &self.cipher_suite_provider)?,
991         }))
992     }
993 
994     /// Create a proposal message that adds a pre shared key from a previous
995     /// epoch to the current group state.
996     ///
997     /// Each group member will need to have the secret state from `psk_epoch`.
998     /// In particular, the members who joined between `psk_epoch` and the
999     /// current epoch cannot process a commit containing this proposal.
1000     ///
1001     /// `authenticated_data` will be sent unencrypted along with the contents
1002     /// of the proposal message.
1003     #[cfg(all(feature = "by_ref_proposal", feature = "psk"))]
1004     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
propose_resumption_psk( &mut self, psk_epoch: u64, authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>1005     pub async fn propose_resumption_psk(
1006         &mut self,
1007         psk_epoch: u64,
1008         authenticated_data: Vec<u8>,
1009     ) -> Result<MlsMessage, MlsError> {
1010         let key_id = ResumptionPsk {
1011             psk_epoch,
1012             usage: ResumptionPSKUsage::Application,
1013             psk_group_id: PskGroupId(self.group_id().to_vec()),
1014         };
1015 
1016         let proposal = self.psk_proposal(JustPreSharedKeyID::Resumption(key_id))?;
1017         self.proposal_message(proposal, authenticated_data).await
1018     }
1019 
1020     /// Create a proposal message that requests for this group to be
1021     /// reinitialized.
1022     ///
1023     /// Once a [`ReInitProposal`](proposal::ReInitProposal)
1024     /// has been sent, another group member can complete reinitialization of
1025     /// the group by calling [`Group::get_reinit_client`].
1026     ///
1027     /// `authenticated_data` will be sent unencrypted along with the contents
1028     /// of the proposal message.
1029     #[cfg(feature = "by_ref_proposal")]
1030     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
propose_reinit( &mut self, group_id: Option<Vec<u8>>, version: ProtocolVersion, cipher_suite: CipherSuite, extensions: ExtensionList, authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>1031     pub async fn propose_reinit(
1032         &mut self,
1033         group_id: Option<Vec<u8>>,
1034         version: ProtocolVersion,
1035         cipher_suite: CipherSuite,
1036         extensions: ExtensionList,
1037         authenticated_data: Vec<u8>,
1038     ) -> Result<MlsMessage, MlsError> {
1039         let proposal = self.reinit_proposal(group_id, version, cipher_suite, extensions)?;
1040         self.proposal_message(proposal, authenticated_data).await
1041     }
1042 
reinit_proposal( &self, group_id: Option<Vec<u8>>, version: ProtocolVersion, cipher_suite: CipherSuite, extensions: ExtensionList, ) -> Result<Proposal, MlsError>1043     fn reinit_proposal(
1044         &self,
1045         group_id: Option<Vec<u8>>,
1046         version: ProtocolVersion,
1047         cipher_suite: CipherSuite,
1048         extensions: ExtensionList,
1049     ) -> Result<Proposal, MlsError> {
1050         let group_id = group_id.map(Ok).unwrap_or_else(|| {
1051             self.cipher_suite_provider
1052                 .random_bytes_vec(self.cipher_suite_provider.kdf_extract_size())
1053                 .map_err(|e| MlsError::CryptoProviderError(e.into_any_error()))
1054         })?;
1055 
1056         Ok(Proposal::ReInit(ReInitProposal {
1057             group_id,
1058             version,
1059             cipher_suite,
1060             extensions,
1061         }))
1062     }
1063 
1064     /// Create a proposal message that sets extensions stored in the group
1065     /// state.
1066     ///
1067     /// # Warning
1068     ///
1069     /// This function does not create a diff that will be applied to the
1070     /// current set of extension that are in use. In order for an existing
1071     /// extension to not be overwritten by this proposal, it must be included
1072     /// in the new set of extensions being proposed.
1073     ///
1074     ///
1075     /// `authenticated_data` will be sent unencrypted along with the contents
1076     /// of the proposal message.
1077     #[cfg(feature = "by_ref_proposal")]
1078     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
propose_group_context_extensions( &mut self, extensions: ExtensionList, authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>1079     pub async fn propose_group_context_extensions(
1080         &mut self,
1081         extensions: ExtensionList,
1082         authenticated_data: Vec<u8>,
1083     ) -> Result<MlsMessage, MlsError> {
1084         let proposal = self.group_context_extensions_proposal(extensions);
1085         self.proposal_message(proposal, authenticated_data).await
1086     }
1087 
group_context_extensions_proposal(&self, extensions: ExtensionList) -> Proposal1088     fn group_context_extensions_proposal(&self, extensions: ExtensionList) -> Proposal {
1089         Proposal::GroupContextExtensions(extensions)
1090     }
1091 
1092     /// Create a custom proposal message.
1093     ///
1094     /// `authenticated_data` will be sent unencrypted along with the contents
1095     /// of the proposal message.
1096     #[cfg(all(feature = "custom_proposal", feature = "by_ref_proposal"))]
1097     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
propose_custom( &mut self, proposal: CustomProposal, authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>1098     pub async fn propose_custom(
1099         &mut self,
1100         proposal: CustomProposal,
1101         authenticated_data: Vec<u8>,
1102     ) -> Result<MlsMessage, MlsError> {
1103         self.proposal_message(Proposal::Custom(proposal), authenticated_data)
1104             .await
1105     }
1106 
1107     /// Delete all sent and received proposals cached for commit.
1108     #[cfg(feature = "by_ref_proposal")]
clear_proposal_cache(&mut self)1109     pub fn clear_proposal_cache(&mut self) {
1110         self.state.proposals.clear()
1111     }
1112 
1113     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
format_for_wire( &mut self, content: AuthenticatedContent, ) -> Result<MlsMessage, MlsError>1114     pub(crate) async fn format_for_wire(
1115         &mut self,
1116         content: AuthenticatedContent,
1117     ) -> Result<MlsMessage, MlsError> {
1118         #[cfg(feature = "private_message")]
1119         let payload = if content.wire_format == WireFormat::PrivateMessage {
1120             MlsMessagePayload::Cipher(self.create_ciphertext(content).await?)
1121         } else {
1122             MlsMessagePayload::Plain(self.create_plaintext(content).await?)
1123         };
1124         #[cfg(not(feature = "private_message"))]
1125         let payload = MlsMessagePayload::Plain(self.create_plaintext(content).await?);
1126 
1127         Ok(MlsMessage::new(self.protocol_version(), payload))
1128     }
1129 
1130     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
create_plaintext( &self, auth_content: AuthenticatedContent, ) -> Result<PublicMessage, MlsError>1131     async fn create_plaintext(
1132         &self,
1133         auth_content: AuthenticatedContent,
1134     ) -> Result<PublicMessage, MlsError> {
1135         let membership_tag = if matches!(auth_content.content.sender, Sender::Member(_)) {
1136             let tag = self
1137                 .key_schedule
1138                 .get_membership_tag(&auth_content, self.context(), &self.cipher_suite_provider)
1139                 .await?;
1140 
1141             Some(tag)
1142         } else {
1143             None
1144         };
1145 
1146         Ok(PublicMessage {
1147             content: auth_content.content,
1148             auth: auth_content.auth,
1149             membership_tag,
1150         })
1151     }
1152 
1153     #[cfg(feature = "private_message")]
1154     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
create_ciphertext( &mut self, auth_content: AuthenticatedContent, ) -> Result<PrivateMessage, MlsError>1155     async fn create_ciphertext(
1156         &mut self,
1157         auth_content: AuthenticatedContent,
1158     ) -> Result<PrivateMessage, MlsError> {
1159         let padding_mode = self.encryption_options()?.padding_mode;
1160 
1161         let mut encryptor = CiphertextProcessor::new(self, self.cipher_suite_provider.clone());
1162 
1163         encryptor.seal(auth_content, padding_mode).await
1164     }
1165 
1166     /// Encrypt an application message using the current group state.
1167     ///
1168     /// `authenticated_data` will be sent unencrypted along with the contents
1169     /// of the proposal message.
1170     #[cfg(feature = "private_message")]
1171     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
encrypt_application_message( &mut self, message: &[u8], authenticated_data: Vec<u8>, ) -> Result<MlsMessage, MlsError>1172     pub async fn encrypt_application_message(
1173         &mut self,
1174         message: &[u8],
1175         authenticated_data: Vec<u8>,
1176     ) -> Result<MlsMessage, MlsError> {
1177         // A group member that has observed one or more proposals within an epoch MUST send a Commit message
1178         // before sending application data
1179         #[cfg(feature = "by_ref_proposal")]
1180         if !self.state.proposals.is_empty() {
1181             return Err(MlsError::CommitRequired);
1182         }
1183 
1184         let auth_content = AuthenticatedContent::new_signed(
1185             &self.cipher_suite_provider,
1186             self.context(),
1187             Sender::Member(*self.private_tree.self_index),
1188             Content::Application(message.to_vec().into()),
1189             &self.signer,
1190             WireFormat::PrivateMessage,
1191             authenticated_data,
1192         )
1193         .await?;
1194 
1195         self.format_for_wire(auth_content).await
1196     }
1197 
1198     #[cfg(feature = "private_message")]
1199     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
decrypt_incoming_ciphertext( &mut self, message: &PrivateMessage, ) -> Result<AuthenticatedContent, MlsError>1200     async fn decrypt_incoming_ciphertext(
1201         &mut self,
1202         message: &PrivateMessage,
1203     ) -> Result<AuthenticatedContent, MlsError> {
1204         let epoch_id = message.epoch;
1205 
1206         let auth_content = if epoch_id == self.context().epoch {
1207             let content = CiphertextProcessor::new(self, self.cipher_suite_provider.clone())
1208                 .open(message)
1209                 .await?;
1210 
1211             verify_auth_content_signature(
1212                 &self.cipher_suite_provider,
1213                 SignaturePublicKeysContainer::RatchetTree(&self.state.public_tree),
1214                 self.context(),
1215                 &content,
1216                 #[cfg(feature = "by_ref_proposal")]
1217                 &[],
1218             )
1219             .await?;
1220 
1221             Ok::<_, MlsError>(content)
1222         } else {
1223             #[cfg(feature = "prior_epoch")]
1224             {
1225                 let epoch = self
1226                     .state_repo
1227                     .get_epoch_mut(epoch_id)
1228                     .await?
1229                     .ok_or(MlsError::EpochNotFound)?;
1230 
1231                 let content = CiphertextProcessor::new(epoch, self.cipher_suite_provider.clone())
1232                     .open(message)
1233                     .await?;
1234 
1235                 verify_auth_content_signature(
1236                     &self.cipher_suite_provider,
1237                     SignaturePublicKeysContainer::List(&epoch.signature_public_keys),
1238                     &epoch.context,
1239                     &content,
1240                     #[cfg(feature = "by_ref_proposal")]
1241                     &[],
1242                 )
1243                 .await?;
1244 
1245                 Ok(content)
1246             }
1247 
1248             #[cfg(not(feature = "prior_epoch"))]
1249             Err(MlsError::EpochNotFound)
1250         }?;
1251 
1252         Ok(auth_content)
1253     }
1254 
1255     /// Apply a pending commit that was created by [`Group::commit`] or
1256     /// [`CommitBuilder::build`].
1257     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
apply_pending_commit(&mut self) -> Result<CommitMessageDescription, MlsError>1258     pub async fn apply_pending_commit(&mut self) -> Result<CommitMessageDescription, MlsError> {
1259         let pending_commit = self
1260             .pending_commit
1261             .clone()
1262             .ok_or(MlsError::PendingCommitNotFound)?;
1263 
1264         self.process_commit(pending_commit.content, None).await
1265     }
1266 
1267     /// Returns true if a commit has been created but not yet applied
1268     /// with [`Group::apply_pending_commit`] or cleared with [`Group::clear_pending_commit`]
has_pending_commit(&self) -> bool1269     pub fn has_pending_commit(&self) -> bool {
1270         self.pending_commit.is_some()
1271     }
1272 
1273     /// Clear the currently pending commit.
1274     ///
1275     /// This function will automatically be called in the event that a
1276     /// commit message is processed using [`Group::process_incoming_message`]
1277     /// before [`Group::apply_pending_commit`] is called.
clear_pending_commit(&mut self)1278     pub fn clear_pending_commit(&mut self) {
1279         self.pending_commit = None
1280     }
1281 
1282     /// Process an inbound message for this group.
1283     ///
1284     /// # Warning
1285     ///
1286     /// Changes to the group's state as a result of processing `message` will
1287     /// not be persisted by the
1288     /// [`GroupStateStorage`](crate::GroupStateStorage)
1289     /// in use by this group until [`Group::write_to_storage`] is called.
1290     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1291     #[inline(never)]
process_incoming_message( &mut self, message: MlsMessage, ) -> Result<ReceivedMessage, MlsError>1292     pub async fn process_incoming_message(
1293         &mut self,
1294         message: MlsMessage,
1295     ) -> Result<ReceivedMessage, MlsError> {
1296         if let Some(pending) = &self.pending_commit {
1297             let message_hash = MessageHash::compute(&self.cipher_suite_provider, &message).await?;
1298 
1299             if message_hash == pending.commit_message_hash {
1300                 let message_description = self.apply_pending_commit().await?;
1301 
1302                 return Ok(ReceivedMessage::Commit(message_description));
1303             }
1304         }
1305 
1306         #[cfg(feature = "by_ref_proposal")]
1307         if message.wire_format() == WireFormat::PrivateMessage {
1308             let cached_own_proposal = self
1309                 .state
1310                 .proposals
1311                 .get_own(&self.cipher_suite_provider, &message)
1312                 .await?;
1313 
1314             if let Some(cached) = cached_own_proposal {
1315                 return Ok(ReceivedMessage::Proposal(cached));
1316             }
1317         }
1318 
1319         MessageProcessor::process_incoming_message(
1320             self,
1321             message,
1322             #[cfg(feature = "by_ref_proposal")]
1323             true,
1324         )
1325         .await
1326     }
1327 
1328     /// Process an inbound message for this group, providing additional context
1329     /// with a message timestamp.
1330     ///
1331     /// Providing a timestamp is useful when the
1332     /// [`IdentityProvider`](crate::IdentityProvider)
1333     /// in use by the group can determine validity based on a timestamp.
1334     /// For example, this allows for checking X.509 certificate expiration
1335     /// at the time when `message` was received by a server rather than when
1336     /// a specific client asynchronously received `message`
1337     ///
1338     /// # Warning
1339     ///
1340     /// Changes to the group's state as a result of processing `message` will
1341     /// not be persisted by the
1342     /// [`GroupStateStorage`](crate::GroupStateStorage)
1343     /// in use by this group until [`Group::write_to_storage`] is called.
1344     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
process_incoming_message_with_time( &mut self, message: MlsMessage, time: MlsTime, ) -> Result<ReceivedMessage, MlsError>1345     pub async fn process_incoming_message_with_time(
1346         &mut self,
1347         message: MlsMessage,
1348         time: MlsTime,
1349     ) -> Result<ReceivedMessage, MlsError> {
1350         MessageProcessor::process_incoming_message_with_time(
1351             self,
1352             message,
1353             #[cfg(feature = "by_ref_proposal")]
1354             true,
1355             Some(time),
1356         )
1357         .await
1358     }
1359 
1360     /// Find a group member by
1361     /// [identity](crate::IdentityProvider::identity)
1362     ///
1363     /// This function determines identity by calling the
1364     /// [`IdentityProvider`](crate::IdentityProvider)
1365     /// currently in use by the group.
1366     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
member_with_identity(&self, identity: &[u8]) -> Result<Member, MlsError>1367     pub async fn member_with_identity(&self, identity: &[u8]) -> Result<Member, MlsError> {
1368         let tree = &self.state.public_tree;
1369 
1370         #[cfg(feature = "tree_index")]
1371         let index = tree.get_leaf_node_with_identity(identity);
1372 
1373         #[cfg(not(feature = "tree_index"))]
1374         let index = tree
1375             .get_leaf_node_with_identity(
1376                 identity,
1377                 &self.identity_provider(),
1378                 &self.state.context.extensions,
1379             )
1380             .await?;
1381 
1382         let index = index.ok_or(MlsError::MemberNotFound)?;
1383         let node = self.state.public_tree.get_leaf_node(index)?;
1384 
1385         Ok(member_from_leaf_node(node, index))
1386     }
1387 
1388     /// Create a group info message that can be used for external proposals and commits.
1389     ///
1390     /// The returned `GroupInfo` is suitable for one external commit for the current epoch.
1391     /// If `with_tree_in_extension` is set to true, the returned `GroupInfo` contains the
1392     /// ratchet tree and therefore contains all information needed to join the group. Otherwise,
1393     /// the ratchet tree must be obtained separately, e.g. via
1394     /// (ExternalClient::export_tree)[crate::external_client::ExternalGroup::export_tree].
1395     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
group_info_message_allowing_ext_commit( &self, with_tree_in_extension: bool, ) -> Result<MlsMessage, MlsError>1396     pub async fn group_info_message_allowing_ext_commit(
1397         &self,
1398         with_tree_in_extension: bool,
1399     ) -> Result<MlsMessage, MlsError> {
1400         let mut extensions = ExtensionList::new();
1401 
1402         extensions.set_from({
1403             self.key_schedule
1404                 .get_external_key_pair_ext(&self.cipher_suite_provider)
1405                 .await?
1406         })?;
1407 
1408         self.group_info_message_internal(extensions, with_tree_in_extension)
1409             .await
1410     }
1411 
1412     /// Create a group info message that can be used for external proposals.
1413     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
group_info_message( &self, with_tree_in_extension: bool, ) -> Result<MlsMessage, MlsError>1414     pub async fn group_info_message(
1415         &self,
1416         with_tree_in_extension: bool,
1417     ) -> Result<MlsMessage, MlsError> {
1418         self.group_info_message_internal(ExtensionList::new(), with_tree_in_extension)
1419             .await
1420     }
1421 
1422     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
group_info_message_internal( &self, mut initial_extensions: ExtensionList, with_tree_in_extension: bool, ) -> Result<MlsMessage, MlsError>1423     pub async fn group_info_message_internal(
1424         &self,
1425         mut initial_extensions: ExtensionList,
1426         with_tree_in_extension: bool,
1427     ) -> Result<MlsMessage, MlsError> {
1428         if with_tree_in_extension {
1429             initial_extensions.set_from(RatchetTreeExt {
1430                 tree_data: ExportedTree::new(self.state.public_tree.nodes.clone()),
1431             })?;
1432         }
1433 
1434         let mut info = GroupInfo {
1435             group_context: self.context().clone(),
1436             extensions: initial_extensions,
1437             confirmation_tag: self.state.confirmation_tag.clone(),
1438             signer: self.private_tree.self_index,
1439             signature: Vec::new(),
1440         };
1441 
1442         info.grease(self.cipher_suite_provider())?;
1443 
1444         info.sign(&self.cipher_suite_provider, &self.signer, &())
1445             .await?;
1446 
1447         Ok(MlsMessage::new(
1448             self.protocol_version(),
1449             MlsMessagePayload::GroupInfo(info),
1450         ))
1451     }
1452 
1453     /// Get the current group context summarizing various information about the group.
1454     #[inline(always)]
context(&self) -> &GroupContext1455     pub fn context(&self) -> &GroupContext {
1456         &self.group_state().context
1457     }
1458 
1459     /// Get the
1460     /// [epoch_authenticator](https://messaginglayersecurity.rocks/mls-protocol/draft-ietf-mls-protocol.html#name-key-schedule)
1461     /// of the current epoch.
epoch_authenticator(&self) -> Result<Secret, MlsError>1462     pub fn epoch_authenticator(&self) -> Result<Secret, MlsError> {
1463         Ok(self.key_schedule.authentication_secret.clone().into())
1464     }
1465 
1466     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
export_secret( &self, label: &[u8], context: &[u8], len: usize, ) -> Result<Secret, MlsError>1467     pub async fn export_secret(
1468         &self,
1469         label: &[u8],
1470         context: &[u8],
1471         len: usize,
1472     ) -> Result<Secret, MlsError> {
1473         self.key_schedule
1474             .export_secret(label, context, len, &self.cipher_suite_provider)
1475             .await
1476             .map(Into::into)
1477     }
1478 
1479     /// Export the current epoch's ratchet tree in serialized format.
1480     ///
1481     /// This function is used to provide the current group tree to new members
1482     /// when the `ratchet_tree_extension` is not used according to [`MlsRules::commit_options`].
export_tree(&self) -> ExportedTree<'_>1483     pub fn export_tree(&self) -> ExportedTree<'_> {
1484         ExportedTree::new_borrowed(&self.current_epoch_tree().nodes)
1485     }
1486 
1487     /// Current version of the MLS protocol in use by this group.
protocol_version(&self) -> ProtocolVersion1488     pub fn protocol_version(&self) -> ProtocolVersion {
1489         self.context().protocol_version
1490     }
1491 
1492     /// Current cipher suite in use by this group.
cipher_suite(&self) -> CipherSuite1493     pub fn cipher_suite(&self) -> CipherSuite {
1494         self.context().cipher_suite
1495     }
1496 
1497     /// Current roster
roster(&self) -> Roster<'_>1498     pub fn roster(&self) -> Roster<'_> {
1499         self.group_state().public_tree.roster()
1500     }
1501 
1502     /// Determines equality of two different groups internal states.
1503     /// Useful for testing.
1504     ///
equal_group_state(a: &Group<C>, b: &Group<C>) -> bool1505     pub fn equal_group_state(a: &Group<C>, b: &Group<C>) -> bool {
1506         a.state == b.state && a.key_schedule == b.key_schedule && a.epoch_secrets == b.epoch_secrets
1507     }
1508 
1509     #[cfg(feature = "psk")]
1510     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
get_psk( &self, psks: &[ProposalInfo<PreSharedKeyProposal>], ) -> Result<(PskSecret, Vec<PreSharedKeyID>), MlsError>1511     async fn get_psk(
1512         &self,
1513         psks: &[ProposalInfo<PreSharedKeyProposal>],
1514     ) -> Result<(PskSecret, Vec<PreSharedKeyID>), MlsError> {
1515         if let Some(psk) = self.previous_psk.clone() {
1516             // TODO consider throwing error if psks not empty
1517             let psk_id = vec![psk.id.clone()];
1518             let psk = PskSecret::calculate(&[psk], self.cipher_suite_provider()).await?;
1519 
1520             Ok((psk, psk_id))
1521         } else {
1522             let psks = psks
1523                 .iter()
1524                 .map(|psk| psk.proposal.psk.clone())
1525                 .collect::<Vec<_>>();
1526 
1527             let psk = PskResolver {
1528                 group_context: Some(self.context()),
1529                 current_epoch: Some(&self.epoch_secrets),
1530                 prior_epochs: Some(&self.state_repo),
1531                 psk_store: &self.config.secret_store(),
1532             }
1533             .resolve_to_secret(&psks, self.cipher_suite_provider())
1534             .await?;
1535 
1536             Ok((psk, psks))
1537         }
1538     }
1539 
1540     #[cfg(feature = "private_message")]
encryption_options(&self) -> Result<EncryptionOptions, MlsError>1541     pub(crate) fn encryption_options(&self) -> Result<EncryptionOptions, MlsError> {
1542         self.config
1543             .mls_rules()
1544             .encryption_options(&self.roster(), self.group_context().extensions())
1545             .map_err(|e| MlsError::MlsRulesError(e.into_any_error()))
1546     }
1547 
1548     #[cfg(not(feature = "psk"))]
get_psk(&self) -> PskSecret1549     fn get_psk(&self) -> PskSecret {
1550         PskSecret::new(self.cipher_suite_provider())
1551     }
1552 
1553     #[cfg(feature = "secret_tree_access")]
1554     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1555     #[inline(never)]
next_encryption_key(&mut self) -> Result<MessageKey, MlsError>1556     pub async fn next_encryption_key(&mut self) -> Result<MessageKey, MlsError> {
1557         self.epoch_secrets
1558             .secret_tree
1559             .next_message_key(
1560                 &self.cipher_suite_provider,
1561                 crate::tree_kem::node::NodeIndex::from(self.private_tree.self_index),
1562                 KeyType::Application,
1563             )
1564             .await
1565     }
1566 
1567     #[cfg(feature = "secret_tree_access")]
1568     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
derive_decryption_key( &mut self, sender: u32, generation: u32, ) -> Result<MessageKey, MlsError>1569     pub async fn derive_decryption_key(
1570         &mut self,
1571         sender: u32,
1572         generation: u32,
1573     ) -> Result<MessageKey, MlsError> {
1574         self.epoch_secrets
1575             .secret_tree
1576             .message_key_generation(
1577                 &self.cipher_suite_provider,
1578                 crate::tree_kem::node::NodeIndex::from(sender),
1579                 KeyType::Application,
1580                 generation,
1581             )
1582             .await
1583     }
1584 }
1585 
1586 #[cfg(feature = "private_message")]
1587 impl<C> GroupStateProvider for Group<C>
1588 where
1589     C: ClientConfig + Clone,
1590 {
group_context(&self) -> &GroupContext1591     fn group_context(&self) -> &GroupContext {
1592         self.context()
1593     }
1594 
self_index(&self) -> LeafIndex1595     fn self_index(&self) -> LeafIndex {
1596         self.private_tree.self_index
1597     }
1598 
epoch_secrets_mut(&mut self) -> &mut EpochSecrets1599     fn epoch_secrets_mut(&mut self) -> &mut EpochSecrets {
1600         &mut self.epoch_secrets
1601     }
1602 
epoch_secrets(&self) -> &EpochSecrets1603     fn epoch_secrets(&self) -> &EpochSecrets {
1604         &self.epoch_secrets
1605     }
1606 }
1607 
1608 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1609 #[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
1610 #[cfg_attr(
1611     all(not(target_arch = "wasm32"), mls_build_async),
1612     maybe_async::must_be_async
1613 )]
1614 impl<C> MessageProcessor for Group<C>
1615 where
1616     C: ClientConfig + Clone,
1617 {
1618     type MlsRules = C::MlsRules;
1619     type IdentityProvider = C::IdentityProvider;
1620     type PreSharedKeyStorage = C::PskStore;
1621     type OutputType = ReceivedMessage;
1622     type CipherSuiteProvider = <C::CryptoProvider as CryptoProvider>::CipherSuiteProvider;
1623 
1624     #[cfg(feature = "private_message")]
process_ciphertext( &mut self, cipher_text: &PrivateMessage, ) -> Result<EventOrContent<Self::OutputType>, MlsError>1625     async fn process_ciphertext(
1626         &mut self,
1627         cipher_text: &PrivateMessage,
1628     ) -> Result<EventOrContent<Self::OutputType>, MlsError> {
1629         self.decrypt_incoming_ciphertext(cipher_text)
1630             .await
1631             .map(EventOrContent::Content)
1632     }
1633 
verify_plaintext_authentication( &self, message: PublicMessage, ) -> Result<EventOrContent<Self::OutputType>, MlsError>1634     async fn verify_plaintext_authentication(
1635         &self,
1636         message: PublicMessage,
1637     ) -> Result<EventOrContent<Self::OutputType>, MlsError> {
1638         let auth_content = verify_plaintext_authentication(
1639             &self.cipher_suite_provider,
1640             message,
1641             Some(&self.key_schedule),
1642             &self.state,
1643         )
1644         .await?;
1645 
1646         Ok(EventOrContent::Content(auth_content))
1647     }
1648 
apply_update_path( &mut self, sender: LeafIndex, update_path: &ValidatedUpdatePath, provisional_state: &mut ProvisionalState, ) -> Result<Option<(TreeKemPrivate, PathSecret)>, MlsError>1649     async fn apply_update_path(
1650         &mut self,
1651         sender: LeafIndex,
1652         update_path: &ValidatedUpdatePath,
1653         provisional_state: &mut ProvisionalState,
1654     ) -> Result<Option<(TreeKemPrivate, PathSecret)>, MlsError> {
1655         // Update the private tree to create a provisional private tree
1656         let (mut provisional_private_tree, new_signer) =
1657             self.provisional_private_tree(provisional_state)?;
1658 
1659         if let Some(signer) = new_signer {
1660             self.signer = signer;
1661         }
1662 
1663         provisional_state
1664             .public_tree
1665             .apply_update_path(
1666                 sender,
1667                 update_path,
1668                 &provisional_state.group_context.extensions,
1669                 self.identity_provider(),
1670                 self.cipher_suite_provider(),
1671             )
1672             .await?;
1673 
1674         if sender == self.private_tree.self_index {
1675             let pending = self
1676                 .pending_commit
1677                 .as_ref()
1678                 .ok_or(MlsError::CantProcessMessageFromSelf)?;
1679 
1680             Ok(Some((
1681                 pending.pending_private_tree.clone(),
1682                 pending.pending_commit_secret.clone(),
1683             )))
1684         } else {
1685             // Update the tree hash to get context for decryption
1686             provisional_state.group_context.tree_hash = provisional_state
1687                 .public_tree
1688                 .tree_hash(&self.cipher_suite_provider)
1689                 .await?;
1690 
1691             let context_bytes = provisional_state.group_context.mls_encode_to_vec()?;
1692 
1693             TreeKem::new(
1694                 &mut provisional_state.public_tree,
1695                 &mut provisional_private_tree,
1696             )
1697             .decap(
1698                 sender,
1699                 update_path,
1700                 &provisional_state.indexes_of_added_kpkgs,
1701                 &context_bytes,
1702                 &self.cipher_suite_provider,
1703             )
1704             .await
1705             .map(|root_secret| Some((provisional_private_tree, root_secret)))
1706         }
1707     }
1708 
update_key_schedule( &mut self, secrets: Option<(TreeKemPrivate, PathSecret)>, interim_transcript_hash: InterimTranscriptHash, confirmation_tag: &ConfirmationTag, provisional_state: ProvisionalState, ) -> Result<(), MlsError>1709     async fn update_key_schedule(
1710         &mut self,
1711         secrets: Option<(TreeKemPrivate, PathSecret)>,
1712         interim_transcript_hash: InterimTranscriptHash,
1713         confirmation_tag: &ConfirmationTag,
1714         provisional_state: ProvisionalState,
1715     ) -> Result<(), MlsError> {
1716         let commit_secret = if let Some(secrets) = secrets {
1717             self.private_tree = secrets.0;
1718             secrets.1
1719         } else {
1720             PathSecret::empty(&self.cipher_suite_provider)
1721         };
1722 
1723         // Use the commit_secret, the psk_secret, the provisional GroupContext, and the init secret
1724         // from the previous epoch (or from the external init) to compute the epoch secret and
1725         // derived secrets for the new epoch
1726 
1727         let key_schedule = match provisional_state
1728             .applied_proposals
1729             .external_initializations
1730             .first()
1731             .cloned()
1732         {
1733             Some(ext_init) if self.pending_commit.is_none() => {
1734                 self.key_schedule
1735                     .derive_for_external(&ext_init.proposal.kem_output, &self.cipher_suite_provider)
1736                     .await?
1737             }
1738             _ => self.key_schedule.clone(),
1739         };
1740 
1741         #[cfg(feature = "psk")]
1742         let (psk, _) = self
1743             .get_psk(&provisional_state.applied_proposals.psks)
1744             .await?;
1745 
1746         #[cfg(not(feature = "psk"))]
1747         let psk = self.get_psk();
1748 
1749         let key_schedule_result = KeySchedule::from_key_schedule(
1750             &key_schedule,
1751             &commit_secret,
1752             &provisional_state.group_context,
1753             #[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
1754             provisional_state.public_tree.total_leaf_count(),
1755             &psk,
1756             &self.cipher_suite_provider,
1757         )
1758         .await?;
1759 
1760         // Use the confirmation_key for the new epoch to compute the confirmation tag for
1761         // this message, as described below, and verify that it is the same as the
1762         // confirmation_tag field in the MlsPlaintext object.
1763         let new_confirmation_tag = ConfirmationTag::create(
1764             &key_schedule_result.confirmation_key,
1765             &provisional_state.group_context.confirmed_transcript_hash,
1766             &self.cipher_suite_provider,
1767         )
1768         .await?;
1769 
1770         if &new_confirmation_tag != confirmation_tag {
1771             return Err(MlsError::InvalidConfirmationTag);
1772         }
1773 
1774         #[cfg(feature = "prior_epoch")]
1775         let signature_public_keys = self
1776             .state
1777             .public_tree
1778             .leaves()
1779             .map(|l| l.map(|n| n.signing_identity.signature_key.clone()))
1780             .collect();
1781 
1782         #[cfg(feature = "prior_epoch")]
1783         let past_epoch = PriorEpoch {
1784             context: self.context().clone(),
1785             self_index: self.private_tree.self_index,
1786             secrets: self.epoch_secrets.clone(),
1787             signature_public_keys,
1788         };
1789 
1790         #[cfg(feature = "prior_epoch")]
1791         self.state_repo.insert(past_epoch).await?;
1792 
1793         self.epoch_secrets = key_schedule_result.epoch_secrets;
1794         self.state.context = provisional_state.group_context;
1795         self.state.interim_transcript_hash = interim_transcript_hash;
1796         self.key_schedule = key_schedule_result.key_schedule;
1797         self.state.public_tree = provisional_state.public_tree;
1798         self.state.confirmation_tag = new_confirmation_tag;
1799 
1800         // Clear the proposals list
1801         #[cfg(feature = "by_ref_proposal")]
1802         self.state.proposals.clear();
1803 
1804         // Clear the pending updates list
1805         #[cfg(feature = "by_ref_proposal")]
1806         {
1807             self.pending_updates = Default::default();
1808         }
1809 
1810         self.pending_commit = None;
1811 
1812         Ok(())
1813     }
1814 
mls_rules(&self) -> Self::MlsRules1815     fn mls_rules(&self) -> Self::MlsRules {
1816         self.config.mls_rules()
1817     }
1818 
identity_provider(&self) -> Self::IdentityProvider1819     fn identity_provider(&self) -> Self::IdentityProvider {
1820         self.config.identity_provider()
1821     }
1822 
psk_storage(&self) -> Self::PreSharedKeyStorage1823     fn psk_storage(&self) -> Self::PreSharedKeyStorage {
1824         self.config.secret_store()
1825     }
1826 
group_state(&self) -> &GroupState1827     fn group_state(&self) -> &GroupState {
1828         &self.state
1829     }
1830 
group_state_mut(&mut self) -> &mut GroupState1831     fn group_state_mut(&mut self) -> &mut GroupState {
1832         &mut self.state
1833     }
1834 
can_continue_processing(&self, provisional_state: &ProvisionalState) -> bool1835     fn can_continue_processing(&self, provisional_state: &ProvisionalState) -> bool {
1836         !(provisional_state
1837             .applied_proposals
1838             .removals
1839             .iter()
1840             .any(|p| p.proposal.to_remove == self.private_tree.self_index)
1841             && self.pending_commit.is_none())
1842     }
1843 
1844     #[cfg(feature = "private_message")]
min_epoch_available(&self) -> Option<u64>1845     fn min_epoch_available(&self) -> Option<u64> {
1846         None
1847     }
1848 
cipher_suite_provider(&self) -> &Self::CipherSuiteProvider1849     fn cipher_suite_provider(&self) -> &Self::CipherSuiteProvider {
1850         &self.cipher_suite_provider
1851     }
1852 }
1853 
1854 #[cfg(test)]
1855 pub(crate) mod test_utils;
1856 
1857 #[cfg(test)]
1858 mod tests {
1859     use crate::{
1860         client::test_utils::{
1861             test_client_with_key_pkg, test_client_with_key_pkg_custom, TestClientBuilder,
1862             TEST_CIPHER_SUITE, TEST_CUSTOM_PROPOSAL_TYPE, TEST_PROTOCOL_VERSION,
1863         },
1864         client_builder::{test_utils::TestClientConfig, ClientBuilder, MlsConfig},
1865         crypto::test_utils::TestCryptoProvider,
1866         group::{
1867             mls_rules::{CommitDirection, CommitSource},
1868             proposal_filter::ProposalBundle,
1869         },
1870         identity::{
1871             basic::BasicIdentityProvider,
1872             test_utils::{get_test_signing_identity, BasicWithCustomProvider},
1873         },
1874         key_package::test_utils::test_key_package_message,
1875         mls_rules::CommitOptions,
1876         tree_kem::{
1877             leaf_node::{test_utils::get_test_capabilities, LeafNodeSource},
1878             UpdatePathNode,
1879         },
1880     };
1881 
1882     #[cfg(any(feature = "private_message", feature = "custom_proposal"))]
1883     use crate::group::mls_rules::DefaultMlsRules;
1884 
1885     #[cfg(feature = "prior_epoch")]
1886     use crate::group::padding::PaddingMode;
1887 
1888     use crate::{extension::RequiredCapabilitiesExt, key_package::test_utils::test_key_package};
1889 
1890     #[cfg(all(feature = "by_ref_proposal", feature = "custom_proposal"))]
1891     use super::test_utils::test_group_custom_config;
1892 
1893     #[cfg(feature = "psk")]
1894     use crate::{client::Client, psk::PreSharedKey};
1895 
1896     #[cfg(any(feature = "by_ref_proposal", feature = "private_message"))]
1897     use crate::group::test_utils::random_bytes;
1898 
1899     #[cfg(feature = "by_ref_proposal")]
1900     use crate::{
1901         extension::test_utils::TestExtension, identity::test_utils::get_test_basic_credential,
1902         time::MlsTime,
1903     };
1904 
1905     use super::{
1906         test_utils::{
1907             get_test_25519_key, get_test_groups_with_features, group_extensions, process_commit,
1908             test_group, test_group_custom, test_n_member_group, TestGroup, TEST_GROUP,
1909         },
1910         *,
1911     };
1912 
1913     use assert_matches::assert_matches;
1914 
1915     use mls_rs_core::extension::{Extension, ExtensionType};
1916     use mls_rs_core::identity::{Credential, CredentialType, CustomCredential};
1917 
1918     #[cfg(feature = "by_ref_proposal")]
1919     use mls_rs_core::identity::CertificateChain;
1920 
1921     #[cfg(feature = "state_update")]
1922     use itertools::Itertools;
1923 
1924     #[cfg(feature = "state_update")]
1925     use alloc::format;
1926 
1927     #[cfg(feature = "by_ref_proposal")]
1928     use crate::{crypto::test_utils::test_cipher_suite_provider, extension::ExternalSendersExt};
1929 
1930     #[cfg(any(feature = "private_message", feature = "state_update"))]
1931     use super::test_utils::test_member;
1932 
1933     use mls_rs_core::extension::MlsExtension;
1934 
1935     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_create_group()1936     async fn test_create_group() {
1937         for (protocol_version, cipher_suite) in ProtocolVersion::all().flat_map(|p| {
1938             TestCryptoProvider::all_supported_cipher_suites()
1939                 .into_iter()
1940                 .map(move |cs| (p, cs))
1941         }) {
1942             let test_group = test_group(protocol_version, cipher_suite).await;
1943             let group = test_group.group;
1944 
1945             assert_eq!(group.cipher_suite(), cipher_suite);
1946             assert_eq!(group.state.context.epoch, 0);
1947             assert_eq!(group.state.context.group_id, TEST_GROUP.to_vec());
1948             assert_eq!(group.state.context.extensions, group_extensions());
1949 
1950             assert_eq!(
1951                 group.state.context.confirmed_transcript_hash,
1952                 ConfirmedTranscriptHash::from(vec![])
1953             );
1954 
1955             #[cfg(feature = "private_message")]
1956             assert!(group.state.proposals.is_empty());
1957 
1958             #[cfg(feature = "by_ref_proposal")]
1959             assert!(group.pending_updates.is_empty());
1960 
1961             assert!(!group.has_pending_commit());
1962 
1963             assert_eq!(
1964                 group.private_tree.self_index.0,
1965                 group.current_member_index()
1966             );
1967         }
1968     }
1969 
1970     #[cfg(feature = "private_message")]
1971     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_pending_proposals_application_data()1972     async fn test_pending_proposals_application_data() {
1973         let mut test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1974 
1975         // Create a proposal
1976         let (bob_key_package, _) =
1977             test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
1978 
1979         let proposal = test_group
1980             .group
1981             .add_proposal(bob_key_package.key_package_message())
1982             .unwrap();
1983 
1984         test_group
1985             .group
1986             .proposal_message(proposal, vec![])
1987             .await
1988             .unwrap();
1989 
1990         // We should not be able to send application messages until a commit happens
1991         let res = test_group
1992             .group
1993             .encrypt_application_message(b"test", vec![])
1994             .await;
1995 
1996         assert_matches!(res, Err(MlsError::CommitRequired));
1997 
1998         // We should be able to send application messages after a commit
1999         test_group.group.commit(vec![]).await.unwrap();
2000 
2001         assert!(test_group.group.has_pending_commit());
2002 
2003         test_group.group.apply_pending_commit().await.unwrap();
2004 
2005         let res = test_group
2006             .group
2007             .encrypt_application_message(b"test", vec![])
2008             .await;
2009 
2010         assert!(res.is_ok());
2011     }
2012 
2013     #[cfg(feature = "by_ref_proposal")]
2014     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_update_proposals()2015     async fn test_update_proposals() {
2016         let new_extension = TestExtension { foo: 10 };
2017         let mut extension_list = ExtensionList::default();
2018         extension_list.set_from(new_extension).unwrap();
2019 
2020         let mut test_group = test_group_custom(
2021             TEST_PROTOCOL_VERSION,
2022             TEST_CIPHER_SUITE,
2023             vec![42.into()],
2024             Some(extension_list.clone()),
2025             None,
2026         )
2027         .await;
2028 
2029         let existing_leaf = test_group.group.current_user_leaf_node().unwrap().clone();
2030 
2031         // Create an update proposal
2032         let proposal = test_group.update_proposal().await;
2033 
2034         let update = match proposal {
2035             Proposal::Update(update) => update,
2036             _ => panic!("non update proposal found"),
2037         };
2038 
2039         assert_ne!(update.leaf_node.public_key, existing_leaf.public_key);
2040 
2041         assert_eq!(
2042             update.leaf_node.signing_identity,
2043             existing_leaf.signing_identity
2044         );
2045 
2046         assert_eq!(update.leaf_node.ungreased_extensions(), extension_list);
2047         assert_eq!(
2048             update.leaf_node.ungreased_capabilities().sorted(),
2049             Capabilities {
2050                 extensions: vec![42.into()],
2051                 ..get_test_capabilities()
2052             }
2053             .sorted()
2054         );
2055     }
2056 
2057     #[cfg(feature = "by_ref_proposal")]
2058     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_invalid_commit_self_update()2059     async fn test_invalid_commit_self_update() {
2060         let mut test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2061 
2062         // Create an update proposal
2063         let proposal_msg = test_group.group.propose_update(vec![]).await.unwrap();
2064 
2065         let proposal = match proposal_msg.into_plaintext().unwrap().content.content {
2066             Content::Proposal(p) => p,
2067             _ => panic!("found non-proposal message"),
2068         };
2069 
2070         let update_leaf = match *proposal {
2071             Proposal::Update(u) => u.leaf_node,
2072             _ => panic!("found proposal message that isn't an update"),
2073         };
2074 
2075         test_group.group.commit(vec![]).await.unwrap();
2076         test_group.group.apply_pending_commit().await.unwrap();
2077 
2078         // The leaf node should not be the one from the update, because the committer rejects it
2079         assert_ne!(
2080             &update_leaf,
2081             test_group.group.current_user_leaf_node().unwrap()
2082         );
2083     }
2084 
2085     #[cfg(feature = "by_ref_proposal")]
2086     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
update_proposal_with_bad_key_package_is_ignored_when_committing()2087     async fn update_proposal_with_bad_key_package_is_ignored_when_committing() {
2088         let (mut alice_group, mut bob_group) =
2089             test_two_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, true).await;
2090 
2091         let mut proposal = alice_group.update_proposal().await;
2092 
2093         if let Proposal::Update(ref mut update) = proposal {
2094             update.leaf_node.signature = random_bytes(32);
2095         } else {
2096             panic!("Invalid update proposal")
2097         }
2098 
2099         let proposal_message = alice_group
2100             .group
2101             .proposal_message(proposal.clone(), vec![])
2102             .await
2103             .unwrap();
2104 
2105         let proposal_plaintext = match proposal_message.payload {
2106             MlsMessagePayload::Plain(p) => p,
2107             _ => panic!("Unexpected non-plaintext message"),
2108         };
2109 
2110         let proposal_ref = ProposalRef::from_content(
2111             &bob_group.group.cipher_suite_provider,
2112             &proposal_plaintext.clone().into(),
2113         )
2114         .await
2115         .unwrap();
2116 
2117         // Hack bob's receipt of the proposal
2118         bob_group.group.state.proposals.insert(
2119             proposal_ref,
2120             proposal,
2121             proposal_plaintext.content.sender,
2122         );
2123 
2124         let commit_output = bob_group.group.commit(vec![]).await.unwrap();
2125 
2126         assert_matches!(
2127             commit_output.commit_message,
2128             MlsMessage {
2129                 payload: MlsMessagePayload::Plain(
2130                     PublicMessage {
2131                         content: FramedContent {
2132                             content: Content::Commit(c),
2133                             ..
2134                         },
2135                         ..
2136                     }),
2137                 ..
2138             } if c.proposals.is_empty()
2139         );
2140     }
2141 
2142     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
test_two_member_group( protocol_version: ProtocolVersion, cipher_suite: CipherSuite, tree_ext: bool, ) -> (TestGroup, TestGroup)2143     async fn test_two_member_group(
2144         protocol_version: ProtocolVersion,
2145         cipher_suite: CipherSuite,
2146         tree_ext: bool,
2147     ) -> (TestGroup, TestGroup) {
2148         let mut test_group = test_group_custom(
2149             protocol_version,
2150             cipher_suite,
2151             Default::default(),
2152             None,
2153             Some(CommitOptions::new().with_ratchet_tree_extension(tree_ext)),
2154         )
2155         .await;
2156 
2157         let (bob_test_group, _) = test_group.join("bob").await;
2158 
2159         assert!(Group::equal_group_state(
2160             &test_group.group,
2161             &bob_test_group.group
2162         ));
2163 
2164         (test_group, bob_test_group)
2165     }
2166 
2167     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_welcome_processing_exported_tree()2168     async fn test_welcome_processing_exported_tree() {
2169         test_two_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, false).await;
2170     }
2171 
2172     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_welcome_processing_tree_extension()2173     async fn test_welcome_processing_tree_extension() {
2174         test_two_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, true).await;
2175     }
2176 
2177     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_welcome_processing_missing_tree()2178     async fn test_welcome_processing_missing_tree() {
2179         let mut test_group = test_group_custom(
2180             TEST_PROTOCOL_VERSION,
2181             TEST_CIPHER_SUITE,
2182             Default::default(),
2183             None,
2184             Some(CommitOptions::new().with_ratchet_tree_extension(false)),
2185         )
2186         .await;
2187 
2188         let (bob_client, bob_key_package) =
2189             test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
2190 
2191         // Add bob to the group
2192         let commit_output = test_group
2193             .group
2194             .commit_builder()
2195             .add_member(bob_key_package)
2196             .unwrap()
2197             .build()
2198             .await
2199             .unwrap();
2200 
2201         // Group from Bob's perspective
2202         let bob_group = Group::join(
2203             &commit_output.welcome_messages[0],
2204             None,
2205             bob_client.config,
2206             bob_client.signer.unwrap(),
2207         )
2208         .await
2209         .map(|_| ());
2210 
2211         assert_matches!(bob_group, Err(MlsError::RatchetTreeNotFound));
2212     }
2213 
2214     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_group_context_ext_proposal_create()2215     async fn test_group_context_ext_proposal_create() {
2216         let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2217 
2218         let mut extension_list = ExtensionList::new();
2219         extension_list
2220             .set_from(RequiredCapabilitiesExt {
2221                 extensions: vec![42.into()],
2222                 proposals: vec![],
2223                 credentials: vec![],
2224             })
2225             .unwrap();
2226 
2227         let proposal = test_group
2228             .group
2229             .group_context_extensions_proposal(extension_list.clone());
2230 
2231         assert_matches!(proposal, Proposal::GroupContextExtensions(ext) if ext == extension_list);
2232     }
2233 
2234     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
group_context_extension_proposal_test( ext_list: ExtensionList, ) -> (TestGroup, Result<MlsMessage, MlsError>)2235     async fn group_context_extension_proposal_test(
2236         ext_list: ExtensionList,
2237     ) -> (TestGroup, Result<MlsMessage, MlsError>) {
2238         let protocol_version = TEST_PROTOCOL_VERSION;
2239         let cipher_suite = TEST_CIPHER_SUITE;
2240 
2241         let mut test_group =
2242             test_group_custom(protocol_version, cipher_suite, vec![42.into()], None, None).await;
2243 
2244         let commit = test_group
2245             .group
2246             .commit_builder()
2247             .set_group_context_ext(ext_list)
2248             .unwrap()
2249             .build()
2250             .await
2251             .map(|commit_output| commit_output.commit_message);
2252 
2253         (test_group, commit)
2254     }
2255 
2256     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_group_context_ext_proposal_commit()2257     async fn test_group_context_ext_proposal_commit() {
2258         let mut extension_list = ExtensionList::new();
2259 
2260         extension_list
2261             .set_from(RequiredCapabilitiesExt {
2262                 extensions: vec![42.into()],
2263                 proposals: vec![],
2264                 credentials: vec![],
2265             })
2266             .unwrap();
2267 
2268         let (mut test_group, _) =
2269             group_context_extension_proposal_test(extension_list.clone()).await;
2270 
2271         #[cfg(feature = "state_update")]
2272         {
2273             let update = test_group.group.apply_pending_commit().await.unwrap();
2274             assert!(update.state_update.active);
2275         }
2276 
2277         #[cfg(not(feature = "state_update"))]
2278         test_group.group.apply_pending_commit().await.unwrap();
2279 
2280         assert_eq!(test_group.group.state.context.extensions, extension_list)
2281     }
2282 
2283     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_group_context_ext_proposal_invalid()2284     async fn test_group_context_ext_proposal_invalid() {
2285         let mut extension_list = ExtensionList::new();
2286         extension_list
2287             .set_from(RequiredCapabilitiesExt {
2288                 extensions: vec![999.into()],
2289                 proposals: vec![],
2290                 credentials: vec![],
2291             })
2292             .unwrap();
2293 
2294         let (_, commit) = group_context_extension_proposal_test(extension_list.clone()).await;
2295 
2296         assert_matches!(
2297             commit,
2298             Err(MlsError::RequiredExtensionNotFound(a)) if a == 999.into()
2299         );
2300     }
2301 
2302     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
make_group_with_required_capabilities( required_caps: RequiredCapabilitiesExt, ) -> Result<Group<TestClientConfig>, MlsError>2303     async fn make_group_with_required_capabilities(
2304         required_caps: RequiredCapabilitiesExt,
2305     ) -> Result<Group<TestClientConfig>, MlsError> {
2306         test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "alice")
2307             .await
2308             .0
2309             .create_group(core::iter::once(required_caps.into_extension().unwrap()).collect())
2310             .await
2311     }
2312 
2313     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
creating_group_with_member_not_supporting_required_credential_type_fails()2314     async fn creating_group_with_member_not_supporting_required_credential_type_fails() {
2315         let group_creation = make_group_with_required_capabilities(RequiredCapabilitiesExt {
2316             credentials: vec![CredentialType::BASIC, CredentialType::X509],
2317             ..Default::default()
2318         })
2319         .await
2320         .map(|_| ());
2321 
2322         assert_matches!(
2323             group_creation,
2324             Err(MlsError::RequiredCredentialNotFound(CredentialType::X509))
2325         );
2326     }
2327 
2328     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
creating_group_with_member_not_supporting_required_extension_type_fails()2329     async fn creating_group_with_member_not_supporting_required_extension_type_fails() {
2330         const EXTENSION_TYPE: ExtensionType = ExtensionType::new(33);
2331 
2332         let group_creation = make_group_with_required_capabilities(RequiredCapabilitiesExt {
2333             extensions: vec![EXTENSION_TYPE],
2334             ..Default::default()
2335         })
2336         .await
2337         .map(|_| ());
2338 
2339         assert_matches!(
2340             group_creation,
2341             Err(MlsError::RequiredExtensionNotFound(EXTENSION_TYPE))
2342         );
2343     }
2344 
2345     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
creating_group_with_member_not_supporting_required_proposal_type_fails()2346     async fn creating_group_with_member_not_supporting_required_proposal_type_fails() {
2347         const PROPOSAL_TYPE: ProposalType = ProposalType::new(33);
2348 
2349         let group_creation = make_group_with_required_capabilities(RequiredCapabilitiesExt {
2350             proposals: vec![PROPOSAL_TYPE],
2351             ..Default::default()
2352         })
2353         .await
2354         .map(|_| ());
2355 
2356         assert_matches!(
2357             group_creation,
2358             Err(MlsError::RequiredProposalNotFound(PROPOSAL_TYPE))
2359         );
2360     }
2361 
2362     #[cfg(feature = "by_ref_proposal")]
2363     #[cfg(not(target_arch = "wasm32"))]
2364     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
creating_group_with_member_not_supporting_external_sender_credential_fails()2365     async fn creating_group_with_member_not_supporting_external_sender_credential_fails() {
2366         let ext_senders = make_x509_external_senders_ext()
2367             .await
2368             .into_extension()
2369             .unwrap();
2370 
2371         let group_creation =
2372             test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "alice")
2373                 .await
2374                 .0
2375                 .create_group(core::iter::once(ext_senders).collect())
2376                 .await
2377                 .map(|_| ());
2378 
2379         assert_matches!(
2380             group_creation,
2381             Err(MlsError::RequiredCredentialNotFound(CredentialType::X509))
2382         );
2383     }
2384 
2385     #[cfg(all(not(target_arch = "wasm32"), feature = "private_message"))]
2386     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_group_encrypt_plaintext_padding()2387     async fn test_group_encrypt_plaintext_padding() {
2388         let protocol_version = TEST_PROTOCOL_VERSION;
2389         // This test requires a cipher suite whose signatures are not variable in length.
2390         let cipher_suite = CipherSuite::CURVE25519_AES128;
2391 
2392         let mut test_group = test_group_custom_config(protocol_version, cipher_suite, |b| {
2393             b.mls_rules(
2394                 DefaultMlsRules::default()
2395                     .with_encryption_options(EncryptionOptions::new(true, PaddingMode::None)),
2396             )
2397         })
2398         .await;
2399 
2400         let without_padding = test_group
2401             .group
2402             .encrypt_application_message(&random_bytes(150), vec![])
2403             .await
2404             .unwrap();
2405 
2406         let mut test_group =
2407             test_group_custom_config(protocol_version, cipher_suite, |b| {
2408                 b.mls_rules(DefaultMlsRules::default().with_encryption_options(
2409                     EncryptionOptions::new(true, PaddingMode::StepFunction),
2410                 ))
2411             })
2412             .await;
2413 
2414         let with_padding = test_group
2415             .group
2416             .encrypt_application_message(&random_bytes(150), vec![])
2417             .await
2418             .unwrap();
2419 
2420         assert!(with_padding.mls_encoded_len() > without_padding.mls_encoded_len());
2421     }
2422 
2423     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
external_commit_requires_external_pub_extension()2424     async fn external_commit_requires_external_pub_extension() {
2425         let protocol_version = TEST_PROTOCOL_VERSION;
2426         let cipher_suite = TEST_CIPHER_SUITE;
2427         let group = test_group(protocol_version, cipher_suite).await;
2428 
2429         let info = group
2430             .group
2431             .group_info_message(false)
2432             .await
2433             .unwrap()
2434             .into_group_info()
2435             .unwrap();
2436 
2437         let info_msg = MlsMessage::new(protocol_version, MlsMessagePayload::GroupInfo(info));
2438 
2439         let signing_identity = group
2440             .group
2441             .current_member_signing_identity()
2442             .unwrap()
2443             .clone();
2444 
2445         let res = external_commit::ExternalCommitBuilder::new(
2446             group.group.signer,
2447             signing_identity,
2448             group.group.config,
2449         )
2450         .build(info_msg)
2451         .await
2452         .map(|_| {});
2453 
2454         assert_matches!(res, Err(MlsError::MissingExternalPubExtension));
2455     }
2456 
2457     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
external_commit_via_commit_options_round_trip()2458     async fn external_commit_via_commit_options_round_trip() {
2459         let mut group = test_group_custom(
2460             TEST_PROTOCOL_VERSION,
2461             TEST_CIPHER_SUITE,
2462             vec![],
2463             None,
2464             CommitOptions::default()
2465                 .with_allow_external_commit(true)
2466                 .into(),
2467         )
2468         .await;
2469 
2470         let commit_output = group.group.commit(vec![]).await.unwrap();
2471 
2472         let (test_client, _) =
2473             test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
2474 
2475         test_client
2476             .external_commit_builder()
2477             .unwrap()
2478             .build(commit_output.external_commit_group_info.unwrap())
2479             .await
2480             .unwrap();
2481     }
2482 
2483     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_path_update_preference()2484     async fn test_path_update_preference() {
2485         let protocol_version = TEST_PROTOCOL_VERSION;
2486         let cipher_suite = TEST_CIPHER_SUITE;
2487 
2488         let mut test_group = test_group_custom(
2489             protocol_version,
2490             cipher_suite,
2491             Default::default(),
2492             None,
2493             Some(CommitOptions::new()),
2494         )
2495         .await;
2496 
2497         let test_key_package =
2498             test_key_package_message(protocol_version, cipher_suite, "alice").await;
2499 
2500         test_group
2501             .group
2502             .commit_builder()
2503             .add_member(test_key_package.clone())
2504             .unwrap()
2505             .build()
2506             .await
2507             .unwrap();
2508 
2509         assert!(test_group
2510             .group
2511             .pending_commit
2512             .unwrap()
2513             .pending_commit_secret
2514             .iter()
2515             .all(|x| x == &0));
2516 
2517         let mut test_group = test_group_custom(
2518             protocol_version,
2519             cipher_suite,
2520             Default::default(),
2521             None,
2522             Some(CommitOptions::new().with_path_required(true)),
2523         )
2524         .await;
2525 
2526         test_group
2527             .group
2528             .commit_builder()
2529             .add_member(test_key_package)
2530             .unwrap()
2531             .build()
2532             .await
2533             .unwrap();
2534 
2535         assert!(!test_group
2536             .group
2537             .pending_commit
2538             .unwrap()
2539             .pending_commit_secret
2540             .iter()
2541             .all(|x| x == &0));
2542     }
2543 
2544     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_path_update_preference_override()2545     async fn test_path_update_preference_override() {
2546         let protocol_version = TEST_PROTOCOL_VERSION;
2547         let cipher_suite = TEST_CIPHER_SUITE;
2548 
2549         let mut test_group = test_group_custom(
2550             protocol_version,
2551             cipher_suite,
2552             Default::default(),
2553             None,
2554             Some(CommitOptions::new()),
2555         )
2556         .await;
2557 
2558         test_group.group.commit(vec![]).await.unwrap();
2559 
2560         assert!(!test_group
2561             .group
2562             .pending_commit
2563             .unwrap()
2564             .pending_commit_secret
2565             .iter()
2566             .all(|x| x == &0));
2567     }
2568 
2569     #[cfg(feature = "private_message")]
2570     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
group_rejects_unencrypted_application_message()2571     async fn group_rejects_unencrypted_application_message() {
2572         let protocol_version = TEST_PROTOCOL_VERSION;
2573         let cipher_suite = TEST_CIPHER_SUITE;
2574 
2575         let mut alice = test_group(protocol_version, cipher_suite).await;
2576         let (mut bob, _) = alice.join("bob").await;
2577 
2578         let message = alice
2579             .make_plaintext(Content::Application(b"hello".to_vec().into()))
2580             .await;
2581 
2582         let res = bob.group.process_incoming_message(message).await;
2583 
2584         assert_matches!(res, Err(MlsError::UnencryptedApplicationMessage));
2585     }
2586 
2587     #[cfg(feature = "state_update")]
2588     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_state_update()2589     async fn test_state_update() {
2590         let protocol_version = TEST_PROTOCOL_VERSION;
2591         let cipher_suite = TEST_CIPHER_SUITE;
2592 
2593         // Create a group with 10 members
2594         let mut alice = test_group(protocol_version, cipher_suite).await;
2595         let (mut bob, _) = alice.join("bob").await;
2596         let mut leaves = vec![];
2597 
2598         for i in 0..8 {
2599             let (group, commit) = alice.join(&format!("charlie{i}")).await;
2600             leaves.push(group.group.current_user_leaf_node().unwrap().clone());
2601             bob.process_message(commit).await.unwrap();
2602         }
2603 
2604         // Create many proposals, make Alice commit them
2605 
2606         let update_message = bob.group.propose_update(vec![]).await.unwrap();
2607 
2608         alice.process_message(update_message).await.unwrap();
2609 
2610         let external_psk_ids: Vec<ExternalPskId> = (0..5)
2611             .map(|i| {
2612                 let external_id = ExternalPskId::new(vec![i]);
2613 
2614                 alice
2615                     .group
2616                     .config
2617                     .secret_store()
2618                     .insert(ExternalPskId::new(vec![i]), PreSharedKey::from(vec![i]));
2619 
2620                 bob.group
2621                     .config
2622                     .secret_store()
2623                     .insert(ExternalPskId::new(vec![i]), PreSharedKey::from(vec![i]));
2624 
2625                 external_id
2626             })
2627             .collect();
2628 
2629         let mut commit_builder = alice.group.commit_builder();
2630 
2631         for external_psk in external_psk_ids {
2632             commit_builder = commit_builder.add_external_psk(external_psk).unwrap();
2633         }
2634 
2635         for index in [2, 5, 6] {
2636             commit_builder = commit_builder.remove_member(index).unwrap();
2637         }
2638 
2639         for i in 0..5 {
2640             let (key_package, _) = test_member(
2641                 protocol_version,
2642                 cipher_suite,
2643                 format!("dave{i}").as_bytes(),
2644             )
2645             .await;
2646 
2647             commit_builder = commit_builder
2648                 .add_member(key_package.key_package_message())
2649                 .unwrap()
2650         }
2651 
2652         let commit_output = commit_builder.build().await.unwrap();
2653 
2654         let commit_description = alice.process_pending_commit().await.unwrap();
2655 
2656         assert!(!commit_description.is_external);
2657 
2658         assert_eq!(
2659             commit_description.committer,
2660             alice.group.current_member_index()
2661         );
2662 
2663         // Check that applying pending commit and processing commit yields correct update.
2664         let state_update_alice = commit_description.state_update.clone();
2665 
2666         assert_eq!(
2667             state_update_alice
2668                 .roster_update
2669                 .added()
2670                 .iter()
2671                 .map(|m| m.index)
2672                 .collect::<Vec<_>>(),
2673             vec![2, 5, 6, 10, 11]
2674         );
2675 
2676         assert_eq!(
2677             state_update_alice.roster_update.removed(),
2678             vec![2, 5, 6]
2679                 .into_iter()
2680                 .map(|i| member_from_leaf_node(&leaves[i as usize - 2], LeafIndex(i)))
2681                 .collect::<Vec<_>>()
2682         );
2683 
2684         assert_eq!(
2685             state_update_alice
2686                 .roster_update
2687                 .updated()
2688                 .iter()
2689                 .map(|update| update.new.clone())
2690                 .collect_vec()
2691                 .as_slice(),
2692             &alice.group.roster().members()[0..2]
2693         );
2694 
2695         assert_eq!(
2696             state_update_alice.added_psks,
2697             (0..5)
2698                 .map(|i| ExternalPskId::new(vec![i]))
2699                 .collect::<Vec<_>>()
2700         );
2701 
2702         let payload = bob
2703             .process_message(commit_output.commit_message)
2704             .await
2705             .unwrap();
2706 
2707         let ReceivedMessage::Commit(bob_commit_description) = payload else {
2708             panic!("expected commit");
2709         };
2710 
2711         assert_eq!(commit_description, bob_commit_description);
2712     }
2713 
2714     #[cfg(feature = "state_update")]
2715     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_description_external_commit()2716     async fn commit_description_external_commit() {
2717         use crate::client::test_utils::TestClientBuilder;
2718 
2719         let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2720 
2721         let (bob_identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;
2722 
2723         let bob = TestClientBuilder::new_for_test()
2724             .signing_identity(bob_identity, secret_key, TEST_CIPHER_SUITE)
2725             .build();
2726 
2727         let (bob_group, commit) = bob
2728             .external_commit_builder()
2729             .unwrap()
2730             .build(
2731                 alice_group
2732                     .group
2733                     .group_info_message_allowing_ext_commit(true)
2734                     .await
2735                     .unwrap(),
2736             )
2737             .await
2738             .unwrap();
2739 
2740         let event = alice_group.process_message(commit).await.unwrap();
2741 
2742         let ReceivedMessage::Commit(commit_description) = event else {
2743             panic!("expected commit");
2744         };
2745 
2746         assert!(commit_description.is_external);
2747         assert_eq!(commit_description.committer, 1);
2748 
2749         assert_eq!(
2750             commit_description.state_update.roster_update.added(),
2751             &bob_group.roster().members()[1..2]
2752         );
2753 
2754         itertools::assert_equal(
2755             bob_group.roster().members_iter(),
2756             alice_group.group.roster().members_iter(),
2757         );
2758     }
2759 
2760     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
can_join_new_group_externally()2761     async fn can_join_new_group_externally() {
2762         use crate::client::test_utils::TestClientBuilder;
2763 
2764         let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2765 
2766         let (bob_identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;
2767 
2768         let bob = TestClientBuilder::new_for_test()
2769             .signing_identity(bob_identity, secret_key, TEST_CIPHER_SUITE)
2770             .build();
2771 
2772         let (_, commit) = bob
2773             .external_commit_builder()
2774             .unwrap()
2775             .with_tree_data(alice_group.group.export_tree().into_owned())
2776             .build(
2777                 alice_group
2778                     .group
2779                     .group_info_message_allowing_ext_commit(false)
2780                     .await
2781                     .unwrap(),
2782             )
2783             .await
2784             .unwrap();
2785 
2786         alice_group.process_message(commit).await.unwrap();
2787     }
2788 
2789     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_membership_tag_from_non_member()2790     async fn test_membership_tag_from_non_member() {
2791         let (mut alice_group, mut bob_group) =
2792             test_two_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, true).await;
2793 
2794         let mut commit_output = alice_group.group.commit(vec![]).await.unwrap();
2795 
2796         let plaintext = match commit_output.commit_message.payload {
2797             MlsMessagePayload::Plain(ref mut plain) => plain,
2798             _ => panic!("Non plaintext message"),
2799         };
2800 
2801         plaintext.content.sender = Sender::NewMemberCommit;
2802 
2803         let res = bob_group
2804             .process_message(commit_output.commit_message)
2805             .await;
2806 
2807         assert_matches!(res, Err(MlsError::MembershipTagForNonMember));
2808     }
2809 
2810     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_partial_commits()2811     async fn test_partial_commits() {
2812         let protocol_version = TEST_PROTOCOL_VERSION;
2813         let cipher_suite = TEST_CIPHER_SUITE;
2814 
2815         let mut alice = test_group(protocol_version, cipher_suite).await;
2816         let (mut bob, _) = alice.join("bob").await;
2817         let (mut charlie, commit) = alice.join("charlie").await;
2818         bob.process_message(commit).await.unwrap();
2819 
2820         let (_, commit) = charlie.join("dave").await;
2821 
2822         alice.process_message(commit.clone()).await.unwrap();
2823         bob.process_message(commit.clone()).await.unwrap();
2824 
2825         let Content::Commit(commit) = commit.into_plaintext().unwrap().content.content else {
2826             panic!("Expected commit")
2827         };
2828 
2829         assert!(commit.path.is_none());
2830     }
2831 
2832     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
group_with_path_required() -> TestGroup2833     async fn group_with_path_required() -> TestGroup {
2834         let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2835 
2836         alice.group.config.0.mls_rules.commit_options.path_required = true;
2837 
2838         alice
2839     }
2840 
2841     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
old_hpke_secrets_are_removed()2842     async fn old_hpke_secrets_are_removed() {
2843         let mut alice = group_with_path_required().await;
2844         alice.join("bob").await;
2845         alice.join("charlie").await;
2846 
2847         alice
2848             .group
2849             .commit_builder()
2850             .remove_member(1)
2851             .unwrap()
2852             .build()
2853             .await
2854             .unwrap();
2855 
2856         assert!(alice.group.private_tree.secret_keys[1].is_some());
2857         alice.process_pending_commit().await.unwrap();
2858         assert!(alice.group.private_tree.secret_keys[1].is_none());
2859     }
2860 
2861     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
old_hpke_secrets_of_removed_are_removed()2862     async fn old_hpke_secrets_of_removed_are_removed() {
2863         let mut alice = group_with_path_required().await;
2864         alice.join("bob").await;
2865         let (mut charlie, _) = alice.join("charlie").await;
2866 
2867         let commit = charlie
2868             .group
2869             .commit_builder()
2870             .remove_member(1)
2871             .unwrap()
2872             .build()
2873             .await
2874             .unwrap();
2875 
2876         assert!(alice.group.private_tree.secret_keys[1].is_some());
2877         alice.process_message(commit.commit_message).await.unwrap();
2878         assert!(alice.group.private_tree.secret_keys[1].is_none());
2879     }
2880 
2881     #[cfg(feature = "by_ref_proposal")]
2882     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
old_hpke_secrets_of_updated_are_removed()2883     async fn old_hpke_secrets_of_updated_are_removed() {
2884         let mut alice = group_with_path_required().await;
2885         let (mut bob, _) = alice.join("bob").await;
2886         let (mut charlie, commit) = alice.join("charlie").await;
2887         bob.process_message(commit).await.unwrap();
2888 
2889         let update = bob.group.propose_update(vec![]).await.unwrap();
2890         charlie.process_message(update.clone()).await.unwrap();
2891         alice.process_message(update).await.unwrap();
2892 
2893         let commit = charlie.group.commit(vec![]).await.unwrap();
2894 
2895         assert!(alice.group.private_tree.secret_keys[1].is_some());
2896         alice.process_message(commit.commit_message).await.unwrap();
2897         assert!(alice.group.private_tree.secret_keys[1].is_none());
2898     }
2899 
2900     #[cfg(feature = "psk")]
2901     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
only_selected_members_of_the_original_group_can_join_subgroup()2902     async fn only_selected_members_of_the_original_group_can_join_subgroup() {
2903         let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2904         let (mut bob, _) = alice.join("bob").await;
2905         let (carol, commit) = alice.join("carol").await;
2906 
2907         // Apply the commit that adds carol
2908         bob.group.process_incoming_message(commit).await.unwrap();
2909 
2910         let bob_identity = bob.group.current_member_signing_identity().unwrap().clone();
2911         let signer = bob.group.signer.clone();
2912 
2913         let new_key_pkg = Client::new(
2914             bob.group.config.clone(),
2915             Some(signer),
2916             Some((bob_identity, TEST_CIPHER_SUITE)),
2917             TEST_PROTOCOL_VERSION,
2918         )
2919         .generate_key_package_message()
2920         .await
2921         .unwrap();
2922 
2923         let (mut alice_sub_group, welcome) = alice
2924             .group
2925             .branch(b"subgroup".to_vec(), vec![new_key_pkg])
2926             .await
2927             .unwrap();
2928 
2929         let welcome = &welcome[0];
2930 
2931         let (mut bob_sub_group, _) = bob.group.join_subgroup(welcome, None).await.unwrap();
2932 
2933         // Carol can't join
2934         let res = carol.group.join_subgroup(welcome, None).await.map(|_| ());
2935         assert_matches!(res, Err(_));
2936 
2937         // Alice and Bob can still talk
2938         let commit_output = alice_sub_group.commit(vec![]).await.unwrap();
2939 
2940         bob_sub_group
2941             .process_incoming_message(commit_output.commit_message)
2942             .await
2943             .unwrap();
2944     }
2945 
2946     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
joining_group_fails_if_unsupported<F>( f: F, ) -> Result<(TestGroup, MlsMessage), MlsError> where F: FnMut(&mut TestClientConfig),2947     async fn joining_group_fails_if_unsupported<F>(
2948         f: F,
2949     ) -> Result<(TestGroup, MlsMessage), MlsError>
2950     where
2951         F: FnMut(&mut TestClientConfig),
2952     {
2953         let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2954         alice_group.join_with_custom_config("alice", false, f).await
2955     }
2956 
2957     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
joining_group_fails_if_protocol_version_is_not_supported()2958     async fn joining_group_fails_if_protocol_version_is_not_supported() {
2959         let res = joining_group_fails_if_unsupported(|config| {
2960             config.0.settings.protocol_versions.clear();
2961         })
2962         .await
2963         .map(|_| ());
2964 
2965         assert_matches!(
2966             res,
2967             Err(MlsError::UnsupportedProtocolVersion(v)) if v ==
2968                 TEST_PROTOCOL_VERSION
2969         );
2970     }
2971 
2972     // WebCrypto does not support disabling ciphersuites
2973     #[cfg(not(target_arch = "wasm32"))]
2974     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
joining_group_fails_if_cipher_suite_is_not_supported()2975     async fn joining_group_fails_if_cipher_suite_is_not_supported() {
2976         let res = joining_group_fails_if_unsupported(|config| {
2977             config
2978                 .0
2979                 .crypto_provider
2980                 .enabled_cipher_suites
2981                 .retain(|&x| x != TEST_CIPHER_SUITE);
2982         })
2983         .await
2984         .map(|_| ());
2985 
2986         assert_matches!(
2987             res,
2988             Err(MlsError::UnsupportedCipherSuite(TEST_CIPHER_SUITE))
2989         );
2990     }
2991 
2992     #[cfg(feature = "private_message")]
2993     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
member_can_see_sender_creds()2994     async fn member_can_see_sender_creds() {
2995         let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2996         let (mut bob_group, _) = alice_group.join("bob").await;
2997 
2998         let bob_msg = b"I'm Bob";
2999 
3000         let msg = bob_group
3001             .group
3002             .encrypt_application_message(bob_msg, vec![])
3003             .await
3004             .unwrap();
3005 
3006         let received_by_alice = alice_group
3007             .group
3008             .process_incoming_message(msg)
3009             .await
3010             .unwrap();
3011 
3012         assert_matches!(
3013             received_by_alice,
3014             ReceivedMessage::ApplicationMessage(ApplicationMessageDescription { sender_index, .. })
3015                 if sender_index == bob_group.group.current_member_index()
3016         );
3017     }
3018 
3019     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
members_of_a_group_have_identical_authentication_secrets()3020     async fn members_of_a_group_have_identical_authentication_secrets() {
3021         let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3022         let (bob_group, _) = alice_group.join("bob").await;
3023 
3024         assert_eq!(
3025             alice_group.group.epoch_authenticator().unwrap(),
3026             bob_group.group.epoch_authenticator().unwrap()
3027         );
3028     }
3029 
3030     #[cfg(feature = "private_message")]
3031     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
member_cannot_decrypt_same_message_twice()3032     async fn member_cannot_decrypt_same_message_twice() {
3033         let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3034         let (mut bob_group, _) = alice_group.join("bob").await;
3035 
3036         let message = alice_group
3037             .group
3038             .encrypt_application_message(b"foobar", Vec::new())
3039             .await
3040             .unwrap();
3041 
3042         let received_message = bob_group
3043             .group
3044             .process_incoming_message(message.clone())
3045             .await
3046             .unwrap();
3047 
3048         assert_matches!(
3049             received_message,
3050             ReceivedMessage::ApplicationMessage(m) if m.data() == b"foobar"
3051         );
3052 
3053         let res = bob_group.group.process_incoming_message(message).await;
3054 
3055         assert_matches!(res, Err(MlsError::KeyMissing(0)));
3056     }
3057 
3058     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
removing_requirements_allows_to_add()3059     async fn removing_requirements_allows_to_add() {
3060         let mut alice_group = test_group_custom(
3061             TEST_PROTOCOL_VERSION,
3062             TEST_CIPHER_SUITE,
3063             vec![17.into()],
3064             None,
3065             None,
3066         )
3067         .await;
3068 
3069         alice_group
3070             .group
3071             .commit_builder()
3072             .set_group_context_ext(
3073                 vec![RequiredCapabilitiesExt {
3074                     extensions: vec![17.into()],
3075                     ..Default::default()
3076                 }
3077                 .into_extension()
3078                 .unwrap()]
3079                 .try_into()
3080                 .unwrap(),
3081             )
3082             .unwrap()
3083             .build()
3084             .await
3085             .unwrap();
3086 
3087         alice_group.process_pending_commit().await.unwrap();
3088 
3089         let test_key_package =
3090             test_key_package(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
3091 
3092         let test_key_package = MlsMessage::new(
3093             TEST_PROTOCOL_VERSION,
3094             MlsMessagePayload::KeyPackage(test_key_package),
3095         );
3096 
3097         alice_group
3098             .group
3099             .commit_builder()
3100             .add_member(test_key_package)
3101             .unwrap()
3102             .set_group_context_ext(Default::default())
3103             .unwrap()
3104             .build()
3105             .await
3106             .unwrap();
3107 
3108         let state_update = alice_group
3109             .process_pending_commit()
3110             .await
3111             .unwrap()
3112             .state_update;
3113 
3114         #[cfg(feature = "state_update")]
3115         assert_eq!(
3116             state_update
3117                 .roster_update
3118                 .added()
3119                 .iter()
3120                 .map(|m| m.index)
3121                 .collect::<Vec<_>>(),
3122             vec![1]
3123         );
3124 
3125         #[cfg(not(feature = "state_update"))]
3126         assert!(state_update == StateUpdate {});
3127 
3128         assert_eq!(alice_group.group.roster().members_iter().count(), 2);
3129     }
3130 
3131     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_wrong_source()3132     async fn commit_leaf_wrong_source() {
3133         // RFC, 13.4.2. "The leaf_node_source field MUST be set to commit."
3134         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 3).await;
3135 
3136         groups[0].group.commit_modifiers.modify_leaf = |leaf, sk| {
3137             leaf.leaf_node_source = LeafNodeSource::Update;
3138             Some(sk.clone())
3139         };
3140 
3141         let commit_output = groups[0].group.commit(vec![]).await.unwrap();
3142 
3143         let res = groups[2]
3144             .process_message(commit_output.commit_message)
3145             .await;
3146 
3147         assert_matches!(res, Err(MlsError::InvalidLeafNodeSource));
3148     }
3149 
3150     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_same_hpke_key()3151     async fn commit_leaf_same_hpke_key() {
3152         // RFC 13.4.2. "Verify that the encryption_key value in the LeafNode is different from the committer's current leaf node"
3153 
3154         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 3).await;
3155 
3156         // Group 0 starts using fixed key
3157         groups[0].group.commit_modifiers.modify_leaf = |leaf, sk| {
3158             leaf.public_key = get_test_25519_key(1u8);
3159             Some(sk.clone())
3160         };
3161 
3162         let commit_output = groups[0].group.commit(vec![]).await.unwrap();
3163         groups[0].process_pending_commit().await.unwrap();
3164         groups[2]
3165             .process_message(commit_output.commit_message)
3166             .await
3167             .unwrap();
3168 
3169         // Group 0 tries to use the fixed key againd
3170         let commit_output = groups[0].group.commit(vec![]).await.unwrap();
3171 
3172         let res = groups[2]
3173             .process_message(commit_output.commit_message)
3174             .await;
3175 
3176         assert_matches!(res, Err(MlsError::SameHpkeKey(0)));
3177     }
3178 
3179     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_duplicate_hpke_key()3180     async fn commit_leaf_duplicate_hpke_key() {
3181         // RFC 8.3 "Verify that the following fields are unique among the members of the group: `encryption_key`"
3182 
3183         if TEST_CIPHER_SUITE != CipherSuite::CURVE25519_AES128
3184             && TEST_CIPHER_SUITE != CipherSuite::CURVE25519_CHACHA
3185         {
3186             return;
3187         }
3188 
3189         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 10).await;
3190 
3191         // Group 1 uses the fixed key
3192         groups[1].group.commit_modifiers.modify_leaf = |leaf, sk| {
3193             leaf.public_key = get_test_25519_key(1u8);
3194             Some(sk.clone())
3195         };
3196 
3197         let commit_output = groups
3198             .get_mut(1)
3199             .unwrap()
3200             .group
3201             .commit(vec![])
3202             .await
3203             .unwrap();
3204 
3205         process_commit(&mut groups, commit_output.commit_message, 1).await;
3206 
3207         // Group 0 tries to use the fixed key too
3208         groups[0].group.commit_modifiers.modify_leaf = |leaf, sk| {
3209             leaf.public_key = get_test_25519_key(1u8);
3210             Some(sk.clone())
3211         };
3212 
3213         let commit_output = groups[0].group.commit(vec![]).await.unwrap();
3214 
3215         let res = groups[7]
3216             .process_message(commit_output.commit_message)
3217             .await;
3218 
3219         assert_matches!(res, Err(MlsError::DuplicateLeafData(_)));
3220     }
3221 
3222     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_duplicate_signature_key()3223     async fn commit_leaf_duplicate_signature_key() {
3224         // RFC 8.3 "Verify that the following fields are unique among the members of the group: `signature_key`"
3225 
3226         if TEST_CIPHER_SUITE != CipherSuite::CURVE25519_AES128
3227             && TEST_CIPHER_SUITE != CipherSuite::CURVE25519_CHACHA
3228         {
3229             return;
3230         }
3231 
3232         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 10).await;
3233 
3234         // Group 1 uses the fixed key
3235         groups[1].group.commit_modifiers.modify_leaf = |leaf, _| {
3236             let sk = hex!(
3237                 "3468b4c890255c983e3d5cbf5cb64c1ef7f6433a518f2f3151d6672f839a06ebcad4fc381fe61822af45135c82921a348e6f46643d66ddefc70483565433714b"
3238             )
3239             .into();
3240 
3241             leaf.signing_identity.signature_key =
3242                 hex!("cad4fc381fe61822af45135c82921a348e6f46643d66ddefc70483565433714b").into();
3243 
3244             Some(sk)
3245         };
3246 
3247         let commit_output = groups
3248             .get_mut(1)
3249             .unwrap()
3250             .group
3251             .commit(vec![])
3252             .await
3253             .unwrap();
3254 
3255         process_commit(&mut groups, commit_output.commit_message, 1).await;
3256 
3257         // Group 0 tries to use the fixed key too
3258         groups[0].group.commit_modifiers.modify_leaf = |leaf, _| {
3259             let sk = hex!(
3260                 "3468b4c890255c983e3d5cbf5cb64c1ef7f6433a518f2f3151d6672f839a06ebcad4fc381fe61822af45135c82921a348e6f46643d66ddefc70483565433714b"
3261             )
3262             .into();
3263 
3264             leaf.signing_identity.signature_key =
3265                 hex!("cad4fc381fe61822af45135c82921a348e6f46643d66ddefc70483565433714b").into();
3266 
3267             Some(sk)
3268         };
3269 
3270         let commit_output = groups[0].group.commit(vec![]).await.unwrap();
3271 
3272         let res = groups[7]
3273             .process_message(commit_output.commit_message)
3274             .await;
3275 
3276         assert_matches!(res, Err(MlsError::DuplicateLeafData(_)));
3277     }
3278 
3279     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_incorrect_signature()3280     async fn commit_leaf_incorrect_signature() {
3281         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 3).await;
3282 
3283         groups[0].group.commit_modifiers.modify_leaf = |leaf, _| {
3284             leaf.signature[0] ^= 1;
3285             None
3286         };
3287 
3288         let commit_output = groups[0].group.commit(vec![]).await.unwrap();
3289 
3290         let res = groups[2]
3291             .process_message(commit_output.commit_message)
3292             .await;
3293 
3294         assert_matches!(res, Err(MlsError::InvalidSignature));
3295     }
3296 
3297     #[cfg(not(target_arch = "wasm32"))]
3298     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_not_supporting_used_context_extension()3299     async fn commit_leaf_not_supporting_used_context_extension() {
3300         const EXT_TYPE: ExtensionType = ExtensionType::new(999);
3301 
3302         // The new leaf of the committer doesn't support an extension set in group context
3303         let extension = Extension::new(EXT_TYPE, vec![]);
3304 
3305         let mut groups =
3306             get_test_groups_with_features(3, vec![extension].into(), Default::default()).await;
3307 
3308         groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
3309             leaf.capabilities = get_test_capabilities();
3310             Some(sk.clone())
3311         };
3312 
3313         let commit_output = groups[0].commit(vec![]).await.unwrap();
3314 
3315         let res = groups[1]
3316             .process_incoming_message(commit_output.commit_message)
3317             .await;
3318 
3319         assert_matches!(res, Err(MlsError::UnsupportedGroupExtension(EXT_TYPE)));
3320     }
3321 
3322     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_not_supporting_required_extension()3323     async fn commit_leaf_not_supporting_required_extension() {
3324         // The new leaf of the committer doesn't support an extension required by group context
3325 
3326         let extension = RequiredCapabilitiesExt {
3327             extensions: vec![999.into()],
3328             proposals: vec![],
3329             credentials: vec![],
3330         };
3331 
3332         let extensions = vec![extension.into_extension().unwrap()];
3333         let mut groups =
3334             get_test_groups_with_features(3, extensions.into(), Default::default()).await;
3335 
3336         groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
3337             leaf.capabilities = Capabilities::default();
3338             Some(sk.clone())
3339         };
3340 
3341         let commit_output = groups[0].commit(vec![]).await.unwrap();
3342 
3343         let res = groups[2]
3344             .process_incoming_message(commit_output.commit_message)
3345             .await;
3346 
3347         assert!(res.is_err());
3348     }
3349 
3350     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_has_unsupported_credential()3351     async fn commit_leaf_has_unsupported_credential() {
3352         // The new leaf of the committer has a credential unsupported by another leaf
3353         let mut groups =
3354             get_test_groups_with_features(3, Default::default(), Default::default()).await;
3355 
3356         for group in groups.iter_mut() {
3357             group.config.0.identity_provider.allow_any_custom = true;
3358         }
3359 
3360         groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
3361             leaf.signing_identity.credential = Credential::Custom(CustomCredential::new(
3362                 CredentialType::new(43),
3363                 leaf.signing_identity
3364                     .credential
3365                     .as_basic()
3366                     .unwrap()
3367                     .identifier
3368                     .to_vec(),
3369             ));
3370 
3371             Some(sk.clone())
3372         };
3373 
3374         let commit_output = groups[0].commit(vec![]).await.unwrap();
3375 
3376         let res = groups[2]
3377             .process_incoming_message(commit_output.commit_message)
3378             .await;
3379 
3380         assert_matches!(res, Err(MlsError::CredentialTypeOfNewLeafIsUnsupported));
3381     }
3382 
3383     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_not_supporting_credential_used_in_another_leaf()3384     async fn commit_leaf_not_supporting_credential_used_in_another_leaf() {
3385         // The new leaf of the committer doesn't support another leaf's credential
3386 
3387         let mut groups =
3388             get_test_groups_with_features(3, Default::default(), Default::default()).await;
3389 
3390         groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
3391             leaf.capabilities.credentials = vec![2.into()];
3392             Some(sk.clone())
3393         };
3394 
3395         let commit_output = groups[0].commit(vec![]).await.unwrap();
3396 
3397         let res = groups[2]
3398             .process_incoming_message(commit_output.commit_message)
3399             .await;
3400 
3401         assert_matches!(res, Err(MlsError::InUseCredentialTypeUnsupportedByNewLeaf));
3402     }
3403 
3404     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_not_supporting_required_credential()3405     async fn commit_leaf_not_supporting_required_credential() {
3406         // The new leaf of the committer doesn't support a credential required by group context
3407 
3408         let extension = RequiredCapabilitiesExt {
3409             extensions: vec![],
3410             proposals: vec![],
3411             credentials: vec![1.into()],
3412         };
3413 
3414         let extensions = vec![extension.into_extension().unwrap()];
3415         let mut groups =
3416             get_test_groups_with_features(3, extensions.into(), Default::default()).await;
3417 
3418         groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
3419             leaf.capabilities.credentials = vec![2.into()];
3420             Some(sk.clone())
3421         };
3422 
3423         let commit_output = groups[0].commit(vec![]).await.unwrap();
3424 
3425         let res = groups[2]
3426             .process_incoming_message(commit_output.commit_message)
3427             .await;
3428 
3429         assert_matches!(res, Err(MlsError::RequiredCredentialNotFound(_)));
3430     }
3431 
3432     #[cfg(feature = "by_ref_proposal")]
3433     #[cfg(not(target_arch = "wasm32"))]
3434     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
make_x509_external_senders_ext() -> ExternalSendersExt3435     async fn make_x509_external_senders_ext() -> ExternalSendersExt {
3436         let (_, ext_sender_pk) = test_cipher_suite_provider(TEST_CIPHER_SUITE)
3437             .signature_key_generate()
3438             .await
3439             .unwrap();
3440 
3441         let ext_sender_id = SigningIdentity {
3442             signature_key: ext_sender_pk,
3443             credential: Credential::X509(CertificateChain::from(vec![random_bytes(32)])),
3444         };
3445 
3446         ExternalSendersExt::new(vec![ext_sender_id])
3447     }
3448 
3449     #[cfg(feature = "by_ref_proposal")]
3450     #[cfg(not(target_arch = "wasm32"))]
3451     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_leaf_not_supporting_external_sender_credential_leads_to_rejected_commit()3452     async fn commit_leaf_not_supporting_external_sender_credential_leads_to_rejected_commit() {
3453         let ext_senders = make_x509_external_senders_ext()
3454             .await
3455             .into_extension()
3456             .unwrap();
3457 
3458         let mut alice = ClientBuilder::new()
3459             .crypto_provider(TestCryptoProvider::new())
3460             .identity_provider(
3461                 BasicWithCustomProvider::default().with_credential_type(CredentialType::X509),
3462             )
3463             .with_random_signing_identity("alice", TEST_CIPHER_SUITE)
3464             .await
3465             .build()
3466             .create_group(core::iter::once(ext_senders).collect())
3467             .await
3468             .unwrap();
3469 
3470         // New leaf supports only basic credentials (used by the group) but not X509 used by external sender
3471         alice.commit_modifiers.modify_leaf = |leaf, sk| {
3472             leaf.capabilities.credentials = vec![CredentialType::BASIC];
3473             Some(sk.clone())
3474         };
3475 
3476         alice.commit(vec![]).await.unwrap();
3477         let res = alice.apply_pending_commit().await;
3478 
3479         assert_matches!(
3480             res,
3481             Err(MlsError::RequiredCredentialNotFound(CredentialType::X509))
3482         );
3483     }
3484 
3485     #[cfg(feature = "by_ref_proposal")]
3486     #[cfg(not(target_arch = "wasm32"))]
3487     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
node_not_supporting_external_sender_credential_cannot_join_group()3488     async fn node_not_supporting_external_sender_credential_cannot_join_group() {
3489         let ext_senders = make_x509_external_senders_ext()
3490             .await
3491             .into_extension()
3492             .unwrap();
3493 
3494         let mut alice = ClientBuilder::new()
3495             .crypto_provider(TestCryptoProvider::new())
3496             .identity_provider(
3497                 BasicWithCustomProvider::default().with_credential_type(CredentialType::X509),
3498             )
3499             .with_random_signing_identity("alice", TEST_CIPHER_SUITE)
3500             .await
3501             .build()
3502             .create_group(core::iter::once(ext_senders).collect())
3503             .await
3504             .unwrap();
3505 
3506         let (_, bob_key_pkg) =
3507             test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
3508 
3509         let commit = alice
3510             .commit_builder()
3511             .add_member(bob_key_pkg)
3512             .unwrap()
3513             .build()
3514             .await;
3515 
3516         assert_matches!(
3517             commit,
3518             Err(MlsError::RequiredCredentialNotFound(CredentialType::X509))
3519         );
3520     }
3521 
3522     #[cfg(feature = "by_ref_proposal")]
3523     #[cfg(not(target_arch = "wasm32"))]
3524     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
external_senders_extension_is_rejected_if_member_does_not_support_credential_type()3525     async fn external_senders_extension_is_rejected_if_member_does_not_support_credential_type() {
3526         let mut alice = ClientBuilder::new()
3527             .crypto_provider(TestCryptoProvider::new())
3528             .identity_provider(
3529                 BasicWithCustomProvider::default().with_credential_type(CredentialType::X509),
3530             )
3531             .with_random_signing_identity("alice", TEST_CIPHER_SUITE)
3532             .await
3533             .build()
3534             .create_group(Default::default())
3535             .await
3536             .unwrap();
3537 
3538         let (_, bob_key_pkg) =
3539             test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
3540 
3541         alice
3542             .commit_builder()
3543             .add_member(bob_key_pkg)
3544             .unwrap()
3545             .build()
3546             .await
3547             .unwrap();
3548 
3549         alice.apply_pending_commit().await.unwrap();
3550         assert_eq!(alice.roster().members_iter().count(), 2);
3551 
3552         let ext_senders = make_x509_external_senders_ext()
3553             .await
3554             .into_extension()
3555             .unwrap();
3556 
3557         let res = alice
3558             .commit_builder()
3559             .set_group_context_ext(core::iter::once(ext_senders).collect())
3560             .unwrap()
3561             .build()
3562             .await;
3563 
3564         assert_matches!(
3565             res,
3566             Err(MlsError::RequiredCredentialNotFound(CredentialType::X509))
3567         );
3568     }
3569 
3570     /*
3571      * Edge case paths
3572      */
3573 
3574     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
committing_degenerate_path_succeeds()3575     async fn committing_degenerate_path_succeeds() {
3576         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 10).await;
3577 
3578         groups[0].group.commit_modifiers.modify_tree = |tree: &mut TreeKemPublic| {
3579             tree.update_node(get_test_25519_key(1u8), 1).unwrap();
3580             tree.update_node(get_test_25519_key(1u8), 3).unwrap();
3581         };
3582 
3583         groups[0].group.commit_modifiers.modify_leaf = |leaf, sk| {
3584             leaf.public_key = get_test_25519_key(1u8);
3585             Some(sk.clone())
3586         };
3587 
3588         let commit_output = groups[0].group.commit(vec![]).await.unwrap();
3589 
3590         let res = groups[7]
3591             .process_message(commit_output.commit_message)
3592             .await;
3593 
3594         assert!(res.is_ok());
3595     }
3596 
3597     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
inserting_key_in_filtered_node_fails()3598     async fn inserting_key_in_filtered_node_fails() {
3599         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 10).await;
3600 
3601         let commit_output = groups[0]
3602             .group
3603             .commit_builder()
3604             .remove_member(1)
3605             .unwrap()
3606             .build()
3607             .await
3608             .unwrap();
3609 
3610         groups[0].process_pending_commit().await.unwrap();
3611 
3612         for group in groups.iter_mut().skip(2) {
3613             group
3614                 .process_message(commit_output.commit_message.clone())
3615                 .await
3616                 .unwrap();
3617         }
3618 
3619         groups[0].group.commit_modifiers.modify_tree = |tree: &mut TreeKemPublic| {
3620             tree.update_node(get_test_25519_key(1u8), 1).unwrap();
3621         };
3622 
3623         groups[0].group.commit_modifiers.modify_path = |path: Vec<UpdatePathNode>| {
3624             let mut path = path;
3625             let mut node = path[0].clone();
3626             node.public_key = get_test_25519_key(1u8);
3627             path.insert(0, node);
3628             path
3629         };
3630 
3631         let commit_output = groups[0].group.commit(vec![]).await.unwrap();
3632 
3633         let res = groups[7]
3634             .process_message(commit_output.commit_message)
3635             .await;
3636 
3637         // We should get a path validation error, since the path is too long
3638         assert_matches!(res, Err(MlsError::WrongPathLen));
3639     }
3640 
3641     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_with_too_short_path_fails()3642     async fn commit_with_too_short_path_fails() {
3643         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 10).await;
3644 
3645         let commit_output = groups[0]
3646             .group
3647             .commit_builder()
3648             .remove_member(1)
3649             .unwrap()
3650             .build()
3651             .await
3652             .unwrap();
3653 
3654         groups[0].process_pending_commit().await.unwrap();
3655 
3656         for group in groups.iter_mut().skip(2) {
3657             group
3658                 .process_message(commit_output.commit_message.clone())
3659                 .await
3660                 .unwrap();
3661         }
3662 
3663         groups[0].group.commit_modifiers.modify_path = |path: Vec<UpdatePathNode>| {
3664             let mut path = path;
3665             path.pop();
3666             path
3667         };
3668 
3669         let commit_output = groups[0].group.commit(vec![]).await.unwrap();
3670 
3671         let res = groups[7]
3672             .process_message(commit_output.commit_message)
3673             .await;
3674 
3675         assert!(res.is_err());
3676     }
3677 
3678     #[cfg(feature = "by_ref_proposal")]
3679     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
update_proposal_can_change_credential()3680     async fn update_proposal_can_change_credential() {
3681         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 3).await;
3682         let (identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"member").await;
3683 
3684         let update = groups[0]
3685             .group
3686             .propose_update_with_identity(secret_key, identity.clone(), vec![])
3687             .await
3688             .unwrap();
3689 
3690         groups[1].process_message(update).await.unwrap();
3691         let commit_output = groups[1].group.commit(vec![]).await.unwrap();
3692 
3693         // Check that the credential was updated by in the committer's state.
3694         groups[1].process_pending_commit().await.unwrap();
3695         let new_member = groups[1].group.roster().member_with_index(0).unwrap();
3696 
3697         assert_eq!(
3698             new_member.signing_identity.credential,
3699             get_test_basic_credential(b"member".to_vec())
3700         );
3701 
3702         assert_eq!(
3703             new_member.signing_identity.signature_key,
3704             identity.signature_key
3705         );
3706 
3707         // Check that the credential was updated in the updater's state.
3708         groups[0]
3709             .process_message(commit_output.commit_message)
3710             .await
3711             .unwrap();
3712         let new_member = groups[0].group.roster().member_with_index(0).unwrap();
3713 
3714         assert_eq!(
3715             new_member.signing_identity.credential,
3716             get_test_basic_credential(b"member".to_vec())
3717         );
3718 
3719         assert_eq!(
3720             new_member.signing_identity.signature_key,
3721             identity.signature_key
3722         );
3723     }
3724 
3725     #[cfg(feature = "by_ref_proposal")]
3726     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
receiving_commit_with_old_adds_fails()3727     async fn receiving_commit_with_old_adds_fails() {
3728         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 2).await;
3729 
3730         let key_package =
3731             test_key_package_message(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "foobar").await;
3732 
3733         let proposal = groups[0]
3734             .group
3735             .propose_add(key_package, vec![])
3736             .await
3737             .unwrap();
3738 
3739         let commit = groups[0].group.commit(vec![]).await.unwrap().commit_message;
3740 
3741         // 10 years from now
3742         let future_time = MlsTime::now().seconds_since_epoch() + 10 * 365 * 24 * 3600;
3743 
3744         let future_time =
3745             MlsTime::from_duration_since_epoch(core::time::Duration::from_secs(future_time));
3746 
3747         groups[1]
3748             .group
3749             .process_incoming_message(proposal)
3750             .await
3751             .unwrap();
3752         let res = groups[1]
3753             .group
3754             .process_incoming_message_with_time(commit, future_time)
3755             .await;
3756 
3757         assert_matches!(res, Err(MlsError::InvalidLifetime));
3758     }
3759 
3760     #[cfg(feature = "custom_proposal")]
3761     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
custom_proposal_setup() -> (TestGroup, TestGroup)3762     async fn custom_proposal_setup() -> (TestGroup, TestGroup) {
3763         let mut alice = test_group_custom_config(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, |b| {
3764             b.custom_proposal_type(TEST_CUSTOM_PROPOSAL_TYPE)
3765         })
3766         .await;
3767 
3768         let (bob, _) = alice
3769             .join_with_custom_config("bob", true, |c| {
3770                 c.0.settings
3771                     .custom_proposal_types
3772                     .push(TEST_CUSTOM_PROPOSAL_TYPE)
3773             })
3774             .await
3775             .unwrap();
3776 
3777         (alice, bob)
3778     }
3779 
3780     #[cfg(feature = "custom_proposal")]
3781     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
custom_proposal_by_value()3782     async fn custom_proposal_by_value() {
3783         let (mut alice, mut bob) = custom_proposal_setup().await;
3784 
3785         let custom_proposal = CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![0, 1, 2]);
3786 
3787         let commit = alice
3788             .group
3789             .commit_builder()
3790             .custom_proposal(custom_proposal.clone())
3791             .build()
3792             .await
3793             .unwrap()
3794             .commit_message;
3795 
3796         let res = bob.group.process_incoming_message(commit).await.unwrap();
3797 
3798         #[cfg(feature = "state_update")]
3799         assert_matches!(res, ReceivedMessage::Commit(CommitMessageDescription { state_update: StateUpdate { custom_proposals, .. }, .. })
3800             if custom_proposals.len() == 1 && custom_proposals[0].proposal == custom_proposal);
3801 
3802         #[cfg(not(feature = "state_update"))]
3803         assert_matches!(res, ReceivedMessage::Commit(_));
3804     }
3805 
3806     #[cfg(feature = "custom_proposal")]
3807     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
custom_proposal_by_reference()3808     async fn custom_proposal_by_reference() {
3809         let (mut alice, mut bob) = custom_proposal_setup().await;
3810 
3811         let custom_proposal = CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![0, 1, 2]);
3812 
3813         let proposal = alice
3814             .group
3815             .propose_custom(custom_proposal.clone(), vec![])
3816             .await
3817             .unwrap();
3818 
3819         let recv_prop = bob.group.process_incoming_message(proposal).await.unwrap();
3820 
3821         assert_matches!(recv_prop, ReceivedMessage::Proposal(ProposalMessageDescription { proposal: Proposal::Custom(c), ..})
3822             if c == custom_proposal);
3823 
3824         let commit = bob.group.commit(vec![]).await.unwrap().commit_message;
3825         let res = alice.group.process_incoming_message(commit).await.unwrap();
3826 
3827         #[cfg(feature = "state_update")]
3828         assert_matches!(res, ReceivedMessage::Commit(CommitMessageDescription { state_update: StateUpdate { custom_proposals, .. }, .. })
3829             if custom_proposals.len() == 1 && custom_proposals[0].proposal == custom_proposal);
3830 
3831         #[cfg(not(feature = "state_update"))]
3832         assert_matches!(res, ReceivedMessage::Commit(_));
3833     }
3834 
3835     #[cfg(feature = "psk")]
3836     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
can_join_with_psk()3837     async fn can_join_with_psk() {
3838         let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE)
3839             .await
3840             .group;
3841 
3842         let (bob, key_pkg) =
3843             test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
3844 
3845         let psk_id = ExternalPskId::new(vec![0]);
3846         let psk = PreSharedKey::from(vec![0]);
3847 
3848         alice
3849             .config
3850             .secret_store()
3851             .insert(psk_id.clone(), psk.clone());
3852 
3853         bob.config.secret_store().insert(psk_id.clone(), psk);
3854 
3855         let commit = alice
3856             .commit_builder()
3857             .add_member(key_pkg)
3858             .unwrap()
3859             .add_external_psk(psk_id)
3860             .unwrap()
3861             .build()
3862             .await
3863             .unwrap();
3864 
3865         bob.join_group(None, &commit.welcome_messages[0])
3866             .await
3867             .unwrap();
3868     }
3869 
3870     #[cfg(feature = "by_ref_proposal")]
3871     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
invalid_update_does_not_prevent_other_updates()3872     async fn invalid_update_does_not_prevent_other_updates() {
3873         const EXTENSION_TYPE: ExtensionType = ExtensionType::new(33);
3874 
3875         let group_extensions = ExtensionList::from(vec![RequiredCapabilitiesExt {
3876             extensions: vec![EXTENSION_TYPE],
3877             ..Default::default()
3878         }
3879         .into_extension()
3880         .unwrap()]);
3881 
3882         // Alice creates a group requiring support for an extension
3883         let mut alice = TestClientBuilder::new_for_test()
3884             .with_random_signing_identity("alice", TEST_CIPHER_SUITE)
3885             .await
3886             .extension_type(EXTENSION_TYPE)
3887             .build()
3888             .create_group(group_extensions.clone())
3889             .await
3890             .unwrap();
3891 
3892         let (bob_signing_identity, bob_secret_key) =
3893             get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;
3894 
3895         let bob_client = TestClientBuilder::new_for_test()
3896             .signing_identity(
3897                 bob_signing_identity.clone(),
3898                 bob_secret_key.clone(),
3899                 TEST_CIPHER_SUITE,
3900             )
3901             .extension_type(EXTENSION_TYPE)
3902             .build();
3903 
3904         let carol_client = TestClientBuilder::new_for_test()
3905             .with_random_signing_identity("carol", TEST_CIPHER_SUITE)
3906             .await
3907             .extension_type(EXTENSION_TYPE)
3908             .build();
3909 
3910         let dave_client = TestClientBuilder::new_for_test()
3911             .with_random_signing_identity("dave", TEST_CIPHER_SUITE)
3912             .await
3913             .extension_type(EXTENSION_TYPE)
3914             .build();
3915 
3916         // Alice adds Bob, Carol and Dave to the group. They all support the mandatory extension.
3917         let commit = alice
3918             .commit_builder()
3919             .add_member(bob_client.generate_key_package_message().await.unwrap())
3920             .unwrap()
3921             .add_member(carol_client.generate_key_package_message().await.unwrap())
3922             .unwrap()
3923             .add_member(dave_client.generate_key_package_message().await.unwrap())
3924             .unwrap()
3925             .build()
3926             .await
3927             .unwrap();
3928 
3929         alice.apply_pending_commit().await.unwrap();
3930 
3931         let mut bob = bob_client
3932             .join_group(None, &commit.welcome_messages[0])
3933             .await
3934             .unwrap()
3935             .0;
3936 
3937         bob.write_to_storage().await.unwrap();
3938 
3939         // Bob reloads his group data, but with parameters that will cause his generated leaves to
3940         // not support the mandatory extension.
3941         let mut bob = TestClientBuilder::new_for_test()
3942             .signing_identity(bob_signing_identity, bob_secret_key, TEST_CIPHER_SUITE)
3943             .key_package_repo(bob.config.key_package_repo())
3944             .group_state_storage(bob.config.group_state_storage())
3945             .build()
3946             .load_group(alice.group_id())
3947             .await
3948             .unwrap();
3949 
3950         let mut carol = carol_client
3951             .join_group(None, &commit.welcome_messages[0])
3952             .await
3953             .unwrap()
3954             .0;
3955 
3956         let mut dave = dave_client
3957             .join_group(None, &commit.welcome_messages[0])
3958             .await
3959             .unwrap()
3960             .0;
3961 
3962         // Bob's updated leaf does not support the mandatory extension.
3963         let bob_update = bob.propose_update(Vec::new()).await.unwrap();
3964         let carol_update = carol.propose_update(Vec::new()).await.unwrap();
3965         let dave_update = dave.propose_update(Vec::new()).await.unwrap();
3966 
3967         // Alice receives the update proposals to be committed.
3968         alice.process_incoming_message(bob_update).await.unwrap();
3969         alice.process_incoming_message(carol_update).await.unwrap();
3970         alice.process_incoming_message(dave_update).await.unwrap();
3971 
3972         // Alice commits the update proposals.
3973         alice.commit(Vec::new()).await.unwrap();
3974         let commit_desc = alice.apply_pending_commit().await.unwrap();
3975 
3976         let find_update_for = |id: &str| {
3977             commit_desc
3978                 .state_update
3979                 .roster_update
3980                 .updated()
3981                 .iter()
3982                 .filter_map(|u| u.prior.signing_identity.credential.as_basic())
3983                 .any(|c| c.identifier == id.as_bytes())
3984         };
3985 
3986         // Check that all updates preserve identities.
3987         let identities_are_preserved = commit_desc
3988             .state_update
3989             .roster_update
3990             .updated()
3991             .iter()
3992             .filter_map(|u| {
3993                 let before = &u.prior.signing_identity.credential.as_basic()?.identifier;
3994                 let after = &u.new.signing_identity.credential.as_basic()?.identifier;
3995                 Some((before, after))
3996             })
3997             .all(|(before, after)| before == after);
3998 
3999         assert!(identities_are_preserved);
4000 
4001         // Carol's and Dave's updates should be part of the commit.
4002         assert!(find_update_for("carol"));
4003         assert!(find_update_for("dave"));
4004 
4005         // Bob's update should be rejected.
4006         assert!(!find_update_for("bob"));
4007 
4008         // Check that all members are still in the group.
4009         let all_members_are_in = alice
4010             .roster()
4011             .members_iter()
4012             .zip(["alice", "bob", "carol", "dave"])
4013             .all(|(member, id)| {
4014                 member
4015                     .signing_identity
4016                     .credential
4017                     .as_basic()
4018                     .unwrap()
4019                     .identifier
4020                     == id.as_bytes()
4021             });
4022 
4023         assert!(all_members_are_in);
4024     }
4025 
4026     #[cfg(feature = "custom_proposal")]
4027     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
custom_proposal_may_enforce_path()4028     async fn custom_proposal_may_enforce_path() {
4029         test_custom_proposal_mls_rules(true).await;
4030     }
4031 
4032     #[cfg(feature = "custom_proposal")]
4033     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
custom_proposal_need_not_enforce_path()4034     async fn custom_proposal_need_not_enforce_path() {
4035         test_custom_proposal_mls_rules(false).await;
4036     }
4037 
4038     #[cfg(feature = "custom_proposal")]
4039     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
test_custom_proposal_mls_rules(path_required_for_custom: bool)4040     async fn test_custom_proposal_mls_rules(path_required_for_custom: bool) {
4041         let mls_rules = CustomMlsRules {
4042             path_required_for_custom,
4043             external_joiner_can_send_custom: true,
4044         };
4045 
4046         let mut alice = client_with_custom_rules(b"alice", mls_rules.clone())
4047             .await
4048             .create_group(Default::default())
4049             .await
4050             .unwrap();
4051 
4052         let alice_pub_before = alice.current_user_leaf_node().unwrap().public_key.clone();
4053 
4054         let kp = client_with_custom_rules(b"bob", mls_rules)
4055             .await
4056             .generate_key_package_message()
4057             .await
4058             .unwrap();
4059 
4060         alice
4061             .commit_builder()
4062             .custom_proposal(CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![]))
4063             .add_member(kp)
4064             .unwrap()
4065             .build()
4066             .await
4067             .unwrap();
4068 
4069         alice.apply_pending_commit().await.unwrap();
4070 
4071         let alice_pub_after = &alice.current_user_leaf_node().unwrap().public_key;
4072 
4073         if path_required_for_custom {
4074             assert_ne!(alice_pub_after, &alice_pub_before);
4075         } else {
4076             assert_eq!(alice_pub_after, &alice_pub_before);
4077         }
4078     }
4079 
4080     #[cfg(feature = "custom_proposal")]
4081     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
custom_proposal_by_value_in_external_join_may_be_allowed()4082     async fn custom_proposal_by_value_in_external_join_may_be_allowed() {
4083         test_custom_proposal_by_value_in_external_join(true).await
4084     }
4085 
4086     #[cfg(feature = "custom_proposal")]
4087     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
custom_proposal_by_value_in_external_join_may_not_be_allowed()4088     async fn custom_proposal_by_value_in_external_join_may_not_be_allowed() {
4089         test_custom_proposal_by_value_in_external_join(false).await
4090     }
4091 
4092     #[cfg(feature = "custom_proposal")]
4093     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
test_custom_proposal_by_value_in_external_join(external_joiner_can_send_custom: bool)4094     async fn test_custom_proposal_by_value_in_external_join(external_joiner_can_send_custom: bool) {
4095         let mls_rules = CustomMlsRules {
4096             path_required_for_custom: true,
4097             external_joiner_can_send_custom,
4098         };
4099 
4100         let mut alice = client_with_custom_rules(b"alice", mls_rules.clone())
4101             .await
4102             .create_group(Default::default())
4103             .await
4104             .unwrap();
4105 
4106         let group_info = alice
4107             .group_info_message_allowing_ext_commit(true)
4108             .await
4109             .unwrap();
4110 
4111         let commit = client_with_custom_rules(b"bob", mls_rules)
4112             .await
4113             .external_commit_builder()
4114             .unwrap()
4115             .with_custom_proposal(CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![]))
4116             .build(group_info)
4117             .await;
4118 
4119         if external_joiner_can_send_custom {
4120             let commit = commit.unwrap().1;
4121             alice.process_incoming_message(commit).await.unwrap();
4122         } else {
4123             assert_matches!(commit.map(|_| ()), Err(MlsError::MlsRulesError(_)));
4124         }
4125     }
4126 
4127     #[cfg(feature = "custom_proposal")]
4128     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
custom_proposal_by_ref_in_external_join()4129     async fn custom_proposal_by_ref_in_external_join() {
4130         let mls_rules = CustomMlsRules {
4131             path_required_for_custom: true,
4132             external_joiner_can_send_custom: true,
4133         };
4134 
4135         let mut alice = client_with_custom_rules(b"alice", mls_rules.clone())
4136             .await
4137             .create_group(Default::default())
4138             .await
4139             .unwrap();
4140 
4141         let by_ref = CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![]);
4142         let by_ref = alice.propose_custom(by_ref, vec![]).await.unwrap();
4143 
4144         let group_info = alice
4145             .group_info_message_allowing_ext_commit(true)
4146             .await
4147             .unwrap();
4148 
4149         let (_, commit) = client_with_custom_rules(b"bob", mls_rules)
4150             .await
4151             .external_commit_builder()
4152             .unwrap()
4153             .with_received_custom_proposal(by_ref)
4154             .build(group_info)
4155             .await
4156             .unwrap();
4157 
4158         alice.process_incoming_message(commit).await.unwrap();
4159     }
4160 
4161     #[cfg(feature = "custom_proposal")]
4162     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
client_with_custom_rules( name: &[u8], mls_rules: CustomMlsRules, ) -> Client<impl MlsConfig>4163     async fn client_with_custom_rules(
4164         name: &[u8],
4165         mls_rules: CustomMlsRules,
4166     ) -> Client<impl MlsConfig> {
4167         let (signing_identity, signer) = get_test_signing_identity(TEST_CIPHER_SUITE, name).await;
4168 
4169         ClientBuilder::new()
4170             .crypto_provider(TestCryptoProvider::new())
4171             .identity_provider(BasicWithCustomProvider::new(BasicIdentityProvider::new()))
4172             .signing_identity(signing_identity, signer, TEST_CIPHER_SUITE)
4173             .custom_proposal_type(TEST_CUSTOM_PROPOSAL_TYPE)
4174             .mls_rules(mls_rules)
4175             .build()
4176     }
4177 
4178     #[derive(Debug, Clone)]
4179     struct CustomMlsRules {
4180         path_required_for_custom: bool,
4181         external_joiner_can_send_custom: bool,
4182     }
4183 
4184     #[cfg(feature = "custom_proposal")]
4185     impl ProposalBundle {
has_test_custom_proposal(&self) -> bool4186         fn has_test_custom_proposal(&self) -> bool {
4187             self.custom_proposal_types()
4188                 .any(|t| t == TEST_CUSTOM_PROPOSAL_TYPE)
4189         }
4190     }
4191 
4192     #[cfg(feature = "custom_proposal")]
4193     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
4194     #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
4195     impl crate::MlsRules for CustomMlsRules {
4196         type Error = MlsError;
4197 
commit_options( &self, _: &Roster, _: &ExtensionList, proposals: &ProposalBundle, ) -> Result<CommitOptions, MlsError>4198         fn commit_options(
4199             &self,
4200             _: &Roster,
4201             _: &ExtensionList,
4202             proposals: &ProposalBundle,
4203         ) -> Result<CommitOptions, MlsError> {
4204             Ok(CommitOptions::default().with_path_required(
4205                 !proposals.has_test_custom_proposal() || self.path_required_for_custom,
4206             ))
4207         }
4208 
encryption_options( &self, _: &Roster, _: &ExtensionList, ) -> Result<crate::mls_rules::EncryptionOptions, MlsError>4209         fn encryption_options(
4210             &self,
4211             _: &Roster,
4212             _: &ExtensionList,
4213         ) -> Result<crate::mls_rules::EncryptionOptions, MlsError> {
4214             Ok(Default::default())
4215         }
4216 
filter_proposals( &self, _: CommitDirection, sender: CommitSource, _: &Roster, _: &ExtensionList, proposals: ProposalBundle, ) -> Result<ProposalBundle, MlsError>4217         async fn filter_proposals(
4218             &self,
4219             _: CommitDirection,
4220             sender: CommitSource,
4221             _: &Roster,
4222             _: &ExtensionList,
4223             proposals: ProposalBundle,
4224         ) -> Result<ProposalBundle, MlsError> {
4225             let is_external = matches!(sender, CommitSource::NewMember(_));
4226             let has_custom = proposals.has_test_custom_proposal();
4227             let allowed = !has_custom || !is_external || self.external_joiner_can_send_custom;
4228 
4229             allowed.then_some(proposals).ok_or(MlsError::InvalidSender)
4230         }
4231     }
4232 
4233     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
group_can_receive_commit_from_self()4234     async fn group_can_receive_commit_from_self() {
4235         let mut group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE)
4236             .await
4237             .group;
4238 
4239         let commit = group.commit(vec![]).await.unwrap();
4240 
4241         let update = group
4242             .process_incoming_message(commit.commit_message)
4243             .await
4244             .unwrap();
4245 
4246         let ReceivedMessage::Commit(update) = update else {
4247             panic!("expected commit message")
4248         };
4249 
4250         assert_eq!(update.committer, *group.private_tree.self_index);
4251     }
4252 
4253     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
can_process_commit_when_pending_commit()4254     async fn can_process_commit_when_pending_commit() {
4255         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 2).await;
4256 
4257         let commit = groups[0].group.commit(vec![]).await.unwrap().commit_message;
4258         groups[1].group.commit(vec![]).await.unwrap();
4259 
4260         groups[1]
4261             .group
4262             .process_incoming_message(commit)
4263             .await
4264             .unwrap();
4265 
4266         let res = groups[1].group.apply_pending_commit().await;
4267         assert_matches!(res, Err(MlsError::PendingCommitNotFound));
4268     }
4269 
4270     #[cfg(feature = "by_ref_proposal")]
4271     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
can_process_own_plaintext_proposal()4272     async fn can_process_own_plaintext_proposal() {
4273         can_process_own_roposal(false).await;
4274     }
4275 
4276     #[cfg(feature = "by_ref_proposal")]
4277     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
can_process_own_ciphertext_proposal()4278     async fn can_process_own_ciphertext_proposal() {
4279         can_process_own_roposal(true).await;
4280     }
4281 
4282     #[cfg(feature = "by_ref_proposal")]
4283     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
can_process_own_roposal(encrypt_proposal: bool)4284     async fn can_process_own_roposal(encrypt_proposal: bool) {
4285         let (alice, _) = test_client_with_key_pkg_custom(
4286             TEST_PROTOCOL_VERSION,
4287             TEST_CIPHER_SUITE,
4288             "alice",
4289             |c| c.0.mls_rules.encryption_options.encrypt_control_messages = encrypt_proposal,
4290         )
4291         .await;
4292 
4293         let mut alice = TestGroup {
4294             group: alice.create_group(Default::default()).await.unwrap(),
4295         };
4296 
4297         let mut bob = alice.join("bob").await.0.group;
4298         let mut alice = alice.group;
4299 
4300         let upd = alice.propose_update(vec![]).await.unwrap();
4301         alice.process_incoming_message(upd.clone()).await.unwrap();
4302 
4303         bob.process_incoming_message(upd).await.unwrap();
4304         let commit = bob.commit(vec![]).await.unwrap().commit_message;
4305         let update = alice.process_incoming_message(commit).await.unwrap();
4306 
4307         let ReceivedMessage::Commit(update) = update else {
4308             panic!("expected commit")
4309         };
4310 
4311         // Check that proposal was applied i.e. alice's index 0 is updated
4312         assert!(update
4313             .state_update
4314             .roster_update
4315             .updated()
4316             .iter()
4317             .any(|member| member.index() == 0));
4318     }
4319 
4320     #[cfg(feature = "by_ref_proposal")]
4321     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
commit_clears_proposals()4322     async fn commit_clears_proposals() {
4323         let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 2).await;
4324 
4325         groups[0].group.propose_update(vec![]).await.unwrap();
4326 
4327         assert_eq!(groups[0].group.state.proposals.proposals.len(), 1);
4328         assert_eq!(groups[0].group.state.proposals.own_proposals.len(), 1);
4329 
4330         let commit = groups[1].group.commit(vec![]).await.unwrap().commit_message;
4331         groups[0].process_message(commit).await.unwrap();
4332 
4333         assert!(groups[0].group.state.proposals.proposals.is_empty());
4334         assert!(groups[0].group.state.proposals.own_proposals.is_empty());
4335     }
4336 }
4337