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