1use alloc::vec;
6use alloc::vec::Vec;
7use core::fmt::{self, Debug};
8use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
9use mls_rs_core::error::IntoAnyError;
10#[cfg(feature = "last_resort_key_package_ext")]
11use mls_rs_core::extension::MlsExtension;
12use mls_rs_core::identity::MemberValidationContext;
13use mls_rs_core::secret::Secret;
14use mls_rs_core::time::MlsTime;
15use snapshot::PendingCommitSnapshot;
16
17use crate::cipher_suite::CipherSuite;
18use crate::client::MlsError;
19use crate::client_config::ClientConfig;
20use crate::crypto::{HpkeCiphertext, SignatureSecretKey};
21#[cfg(feature = "last_resort_key_package_ext")]
22use crate::extension::LastResortKeyPackageExt;
23use crate::extension::RatchetTreeExt;
24use crate::identity::SigningIdentity;
25use crate::key_package::{KeyPackage, KeyPackageGeneration, KeyPackageRef};
26use crate::protocol_version::ProtocolVersion;
27use crate::psk::secret::PskSecret;
28use crate::psk::PreSharedKeyID;
29use crate::signer::Signable;
30use crate::tree_kem::hpke_encryption::HpkeEncryptable;
31use crate::tree_kem::kem::TreeKem;
32use crate::tree_kem::leaf_node::LeafNode;
33use crate::tree_kem::leaf_node_validator::{LeafNodeValidator, ValidationContext};
34use crate::tree_kem::node::LeafIndex;
35use crate::tree_kem::path_secret::PathSecret;
36pub use crate::tree_kem::Capabilities;
37use crate::tree_kem::{math as tree_math, ValidatedUpdatePath};
38use crate::tree_kem::{TreeKemPrivate, TreeKemPublic};
39use crate::{CipherSuiteProvider, CryptoProvider};
40pub use state::GroupState;
41
42#[cfg(feature = "by_ref_proposal")]
43use crate::crypto::{HpkePublicKey, HpkeSecretKey};
44
45use crate::extension::ExternalPubExt;
46
47use self::message_hash::MessageHash;
48#[cfg(feature = "private_message")]
49use self::mls_rules::{EncryptionOptions, MlsRules};
50
51#[cfg(feature = "psk")]
52pub use self::resumption::ReinitClient;
53
54#[cfg(feature = "psk")]
55use crate::psk::{
56 resolver::PskResolver, secret::PskSecretInput, ExternalPskId, JustPreSharedKeyID, PskGroupId,
57 ResumptionPSKUsage, ResumptionPsk,
58};
59
60#[cfg(feature = "private_message")]
61use ciphertext_processor::*;
62
63use component_operation::{ComponentID, ComponentOperationLabel};
64use confirmation_tag::*;
65use framing::*;
66use key_schedule::*;
67use membership_tag::*;
68use message_signature::*;
69use message_verifier::*;
70use proposal::*;
71#[cfg(feature = "by_ref_proposal")]
72use proposal_cache::*;
73use transcript_hash::*;
74
75#[cfg(test)]
76pub(crate) use self::commit::test_utils::CommitModifiers;
77
78#[cfg(all(test, feature = "private_message"))]
79pub use self::framing::PrivateMessage;
80
81use self::proposal_filter::ProposalInfo;
82
83#[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
84use secret_tree::*;
85
86#[cfg(feature = "prior_epoch")]
87use self::epoch::PriorEpoch;
88
89use self::epoch::EpochSecrets;
90pub use self::message_processor::{
91 ApplicationMessageDescription, CommitEffect, CommitMessageDescription, NewEpoch,
92 ProposalMessageDescription, ProposalSender, ReceivedMessage,
93};
94use self::message_processor::{EventOrContent, MessageProcessor, ProvisionalState};
95#[cfg(feature = "by_ref_proposal")]
96use self::proposal_ref::ProposalRef;
97use self::state_repo::GroupStateRepository;
98pub use group_info::GroupInfo;
99
100pub use self::framing::{ContentType, Sender};
101pub use commit::*;
102pub use mls_rs_core::group::GroupContext;
103pub use roster::*;
104
105pub(crate) use mls_rs_core::group::ConfirmedTranscriptHash;
106pub(crate) use util::*;
107
108#[cfg(all(feature = "by_ref_proposal", feature = "external_client"))]
109pub use self::message_processor::CachedProposal;
110
111#[cfg(feature = "private_message")]
112mod ciphertext_processor;
113
114mod commit;
115pub mod component_operation;
116pub(crate) mod confirmation_tag;
117pub(crate) mod epoch;
118pub(crate) mod framing;
119mod group_info;
120pub(crate) mod key_schedule;
121mod membership_tag;
122pub(crate) mod message_hash;
123pub(crate) mod message_processor;
124pub(crate) mod message_signature;
125pub(crate) mod message_verifier;
126pub mod mls_rules;
127#[cfg(feature = "private_message")]
128pub(crate) mod padding;
129pub mod proposal;
131mod proposal_cache;
132pub(crate) mod proposal_filter;
133#[cfg(feature = "by_ref_proposal")]
134pub(crate) mod proposal_ref;
135#[cfg(feature = "psk")]
136mod resumption;
137mod roster;
138pub(crate) mod snapshot;
139pub(crate) mod state;
140
141#[cfg(feature = "prior_epoch")]
142pub(crate) mod state_repo;
143#[cfg(not(feature = "prior_epoch"))]
144pub(crate) mod state_repo_light;
145#[cfg(not(feature = "prior_epoch"))]
146pub(crate) use state_repo_light as state_repo;
147
148pub(crate) mod transcript_hash;
149mod util;
150
151pub mod external_commit;
153
154#[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
155pub(crate) mod secret_tree;
156
157#[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
158pub use secret_tree::MessageKeyData as MessageKey;
159
160#[cfg(all(test, feature = "rfc_compliant"))]
161mod interop_test_vectors;
162
163mod exported_tree;
164
165pub use exported_tree::ExportedTree;
166
167#[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
168struct GroupSecrets {
169 joiner_secret: JoinerSecret,
170 path_secret: Option<PathSecret>,
171 psks: Vec<PreSharedKeyID>,
172}
173
174impl HpkeEncryptable for GroupSecrets {
175 const ENCRYPT_LABEL: &'static str = "Welcome";
176
177 fn from_bytes(bytes: Vec<u8>) -> Result<Self, MlsError> {
178 Self::mls_decode(&mut bytes.as_slice()).map_err(Into::into)
179 }
180
181 fn get_bytes(&self) -> Result<Vec<u8>, MlsError> {
182 self.mls_encode_to_vec().map_err(Into::into)
183 }
184}
185
186#[derive(Clone, Debug, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
187#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
188pub(crate) struct EncryptedGroupSecrets {
189 pub new_member: KeyPackageRef,
190 pub encrypted_group_secrets: HpkeCiphertext,
191}
192
193#[derive(Clone, Eq, PartialEq, MlsSize, MlsEncode, MlsDecode)]
194#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
195pub(crate) struct Welcome {
196 pub cipher_suite: CipherSuite,
197 pub secrets: Vec<EncryptedGroupSecrets>,
198 #[mls_codec(with = "mls_rs_codec::byte_vec")]
199 pub encrypted_group_info: Vec<u8>,
200}
201
202impl Debug for Welcome {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 f.debug_struct("Welcome")
205 .field("cipher_suite", &self.cipher_suite)
206 .field("secrets", &self.secrets)
207 .field(
208 "encrypted_group_info",
209 &mls_rs_core::debug::pretty_bytes(&self.encrypted_group_info),
210 )
211 .finish()
212 }
213}
214
215#[derive(Clone, Debug)]
216#[cfg_attr(
217 all(feature = "ffi", not(test)),
218 safer_ffi_gen::ffi_type(clone, opaque)
219)]
220#[non_exhaustive]
221pub struct NewMemberInfo {
223 pub group_info_extensions: ExtensionList,
226 pub sender: u32,
230}
231
232#[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen)]
233impl NewMemberInfo {
234 pub(crate) fn new(group_info_extensions: ExtensionList, sender: u32) -> Self {
235 let mut new_member_info = Self {
236 group_info_extensions,
237 sender,
238 };
239
240 new_member_info.ungrease();
241
242 new_member_info
243 }
244
245 #[cfg(feature = "ffi")]
248 pub fn group_info_extensions(&self) -> &ExtensionList {
249 &self.group_info_extensions
250 }
251}
252
253#[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::ffi_type(opaque))]
264#[derive(Clone)]
265pub struct Group<C>
266where
267 C: ClientConfig,
268{
269 config: C,
270 cipher_suite_provider: <C::CryptoProvider as CryptoProvider>::CipherSuiteProvider,
271 state_repo: GroupStateRepository<C::GroupStateStorage, C::KeyPackageRepository>,
272 pub(crate) state: GroupState,
273 epoch_secrets: EpochSecrets,
274 private_tree: TreeKemPrivate,
275 key_schedule: KeySchedule,
276 #[cfg(feature = "by_ref_proposal")]
277 pending_updates:
278 crate::map::SmallMap<HpkePublicKey, (HpkeSecretKey, Option<SignatureSecretKey>)>,
279 pending_commit: PendingCommitSnapshot,
280 #[cfg(feature = "psk")]
281 previous_psk: Option<PskSecretInput>,
282 #[cfg(test)]
283 pub(crate) commit_modifiers: CommitModifiers,
284 pub(crate) signer: SignatureSecretKey,
285}
286
287#[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen)]
288impl<C> Group<C>
289where
290 C: ClientConfig + Clone,
291{
292 #[allow(clippy::too_many_arguments)]
293 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
294 pub(crate) async fn new(
295 config: C,
296 group_id: Option<Vec<u8>>,
297 cipher_suite: CipherSuite,
298 protocol_version: ProtocolVersion,
299 signing_identity: SigningIdentity,
300 group_context_extensions: ExtensionList,
301 leaf_node_extensions: ExtensionList,
302 signer: SignatureSecretKey,
303 maybe_now_time: Option<MlsTime>,
304 ) -> Result<Self, MlsError> {
305 let cipher_suite_provider = cipher_suite_provider(config.crypto_provider(), cipher_suite)?;
306
307 let (leaf_node, leaf_node_secret) = LeafNode::generate(
308 &cipher_suite_provider,
309 config.leaf_properties(leaf_node_extensions),
310 signing_identity,
311 &signer,
312 config.lifetime(maybe_now_time),
313 )
314 .await?;
315
316 let (mut public_tree, private_tree) = TreeKemPublic::derive(
317 leaf_node,
318 leaf_node_secret,
319 &config.identity_provider(),
320 &group_context_extensions,
321 )
322 .await?;
323
324 let tree_hash = public_tree.tree_hash(&cipher_suite_provider).await?;
325
326 let group_id = group_id.map(Ok).unwrap_or_else(|| {
327 cipher_suite_provider
328 .random_bytes_vec(cipher_suite_provider.kdf_extract_size())
329 .map_err(|e| MlsError::CryptoProviderError(e.into_any_error()))
330 })?;
331
332 let context = GroupContext::new(
333 protocol_version,
334 cipher_suite,
335 group_id,
336 tree_hash,
337 group_context_extensions,
338 );
339
340 let identity_provider = config.identity_provider();
341
342 let member_validation_context = MemberValidationContext::ForNewGroup {
343 current_context: &context,
344 };
345
346 let leaf_node_validator = LeafNodeValidator::new(
347 &cipher_suite_provider,
348 &identity_provider,
349 member_validation_context,
350 );
351
352 leaf_node_validator
353 .check_if_valid(
354 public_tree.get_leaf_node(LeafIndex::unchecked(0))?,
355 ValidationContext::Add(maybe_now_time),
356 )
357 .await?;
358
359 let state_repo = GroupStateRepository::new(
360 #[cfg(feature = "prior_epoch")]
361 context.group_id.clone(),
362 config.group_state_storage(),
363 config.key_package_repo(),
364 None,
365 )?;
366
367 let key_schedule_result = KeySchedule::from_random_epoch_secret(
368 &cipher_suite_provider,
369 #[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
370 public_tree.total_leaf_count(),
371 )
372 .await?;
373
374 let confirmation_tag = ConfirmationTag::create(
375 &key_schedule_result.confirmation_key,
376 &vec![].into(),
377 &cipher_suite_provider,
378 )
379 .await?;
380
381 let interim_hash = InterimTranscriptHash::create(
382 &cipher_suite_provider,
383 &vec![].into(),
384 &confirmation_tag,
385 )
386 .await?;
387
388 Ok(Self {
389 config,
390 state: GroupState::new(context, public_tree, interim_hash, confirmation_tag),
391 private_tree,
392 key_schedule: key_schedule_result.key_schedule,
393 #[cfg(feature = "by_ref_proposal")]
394 pending_updates: Default::default(),
395 pending_commit: Default::default(),
396 #[cfg(test)]
397 commit_modifiers: Default::default(),
398 epoch_secrets: key_schedule_result.epoch_secrets,
399 state_repo,
400 cipher_suite_provider,
401 #[cfg(feature = "psk")]
402 previous_psk: None,
403 signer,
404 })
405 }
406
407 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
408 pub(crate) async fn join(
409 welcome: &MlsMessage,
410 tree_data: Option<ExportedTree<'_>>,
411 config: C,
412 signer: SignatureSecretKey,
413 maybe_time: Option<MlsTime>,
414 ) -> Result<(Self, NewMemberInfo), MlsError> {
415 Self::from_welcome_message(
416 welcome,
417 tree_data,
418 config,
419 signer,
420 #[cfg(feature = "psk")]
421 None,
422 maybe_time,
423 )
424 .await
425 }
426
427 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
428 async fn from_welcome_message(
429 welcome: &MlsMessage,
430 tree_data: Option<ExportedTree<'_>>,
431 config: C,
432 signer: SignatureSecretKey,
433 #[cfg(feature = "psk")] additional_psk: Option<PskSecretInput>,
434 maybe_time: Option<MlsTime>,
435 ) -> Result<(Self, NewMemberInfo), MlsError> {
436 let (group_info, key_package_generation, group_secrets, psk_secret) =
437 Self::decrypt_group_info_internal(
438 welcome,
439 &config,
440 #[cfg(feature = "psk")]
441 additional_psk,
442 )
443 .await?;
444
445 let cipher_suite_provider = cipher_suite_provider(
446 config.crypto_provider(),
447 group_info.group_context.cipher_suite,
448 )?;
449
450 let id_provider = config.identity_provider();
451
452 let public_tree = validate_tree_and_info_joiner(
453 welcome.version,
454 &group_info,
455 tree_data,
456 &id_provider,
457 &cipher_suite_provider,
458 maybe_time,
459 )
460 .await?;
461
462 let key_package = key_package_generation.key_package;
463
464 let self_index = public_tree
469 .find_leaf_node(&key_package.leaf_node)
470 .ok_or(MlsError::WelcomeKeyPackageNotFound)?;
471
472 #[cfg(not(feature = "last_resort_key_package_ext"))]
473 let is_last_resort = false;
474 #[cfg(feature = "last_resort_key_package_ext")]
475 let is_last_resort = key_package
476 .extensions
477 .has_extension(LastResortKeyPackageExt::extension_type());
478 let used_key_package_ref = (!is_last_resort).then_some(key_package_generation.reference);
480
481 let mut private_tree =
482 TreeKemPrivate::new_self_leaf(self_index, key_package_generation.leaf_node_secret_key);
483
484 if let Some(path_secret) = group_secrets.path_secret {
486 private_tree
487 .update_secrets(
488 &cipher_suite_provider,
489 group_info.signer,
490 path_secret,
491 &public_tree,
492 )
493 .await?;
494 }
495
496 let key_schedule_result = KeySchedule::from_joiner(
499 &cipher_suite_provider,
500 &group_secrets.joiner_secret,
501 &group_info.group_context,
502 #[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
503 public_tree.total_leaf_count(),
504 &psk_secret,
505 )
506 .await?;
507
508 if !group_info
511 .confirmation_tag
512 .matches(
513 &key_schedule_result.confirmation_key,
514 &group_info.group_context.confirmed_transcript_hash,
515 &cipher_suite_provider,
516 )
517 .await?
518 {
519 return Err(MlsError::InvalidConfirmationTag);
520 }
521
522 Self::join_with(
523 config,
524 group_info,
525 public_tree,
526 key_schedule_result.key_schedule,
527 key_schedule_result.epoch_secrets,
528 private_tree,
529 used_key_package_ref,
530 signer,
531 )
532 .await
533 }
534
535 #[allow(clippy::too_many_arguments)]
536 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
537 async fn join_with(
538 config: C,
539 group_info: GroupInfo,
540 public_tree: TreeKemPublic,
541 key_schedule: KeySchedule,
542 epoch_secrets: EpochSecrets,
543 private_tree: TreeKemPrivate,
544 used_key_package_ref: Option<KeyPackageRef>,
545 signer: SignatureSecretKey,
546 ) -> Result<(Self, NewMemberInfo), MlsError> {
547 let cs = group_info.group_context.cipher_suite;
548
549 let cs = config
550 .crypto_provider()
551 .cipher_suite_provider(cs)
552 .ok_or(MlsError::UnsupportedCipherSuite(cs))?;
553
554 let interim_transcript_hash = InterimTranscriptHash::create(
557 &cs,
558 &group_info.group_context.confirmed_transcript_hash,
559 &group_info.confirmation_tag,
560 )
561 .await?;
562
563 let state_repo = GroupStateRepository::new(
564 #[cfg(feature = "prior_epoch")]
565 group_info.group_context.group_id.clone(),
566 config.group_state_storage(),
567 config.key_package_repo(),
568 used_key_package_ref,
569 )?;
570
571 let group = Group {
572 config,
573 state: GroupState::new(
574 group_info.group_context,
575 public_tree,
576 interim_transcript_hash,
577 group_info.confirmation_tag,
578 ),
579 private_tree,
580 key_schedule,
581 #[cfg(feature = "by_ref_proposal")]
582 pending_updates: Default::default(),
583 pending_commit: Default::default(),
584 #[cfg(test)]
585 commit_modifiers: Default::default(),
586 epoch_secrets,
587 state_repo,
588 cipher_suite_provider: cs,
589 #[cfg(feature = "psk")]
590 previous_psk: None,
591 signer,
592 };
593
594 Ok((
595 group,
596 NewMemberInfo::new(group_info.extensions, *group_info.signer),
597 ))
598 }
599
600 #[inline(always)]
601 pub(crate) fn current_epoch_tree(&self) -> &TreeKemPublic {
602 &self.state.public_tree
603 }
604
605 #[inline(always)]
608 pub fn current_epoch(&self) -> u64 {
609 self.context().epoch
610 }
611
612 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
613 async fn hpke_encrypt_to_recipient_with_generic_context(
614 &self,
615 recipient_index: u32,
616 context_info: &[u8],
617 associated_data: Option<&[u8]>,
618 plaintext: &[u8],
619 ) -> Result<HpkeCiphertext, MlsError> {
620 let member_leaf_node = self
621 .group_state()
622 .public_tree
623 .get_leaf_node(LeafIndex::try_from(recipient_index)?)?;
624 let member_public_key = &member_leaf_node.public_key;
625 let hpke_ciphertext = self
626 .cipher_suite_provider
627 .hpke_seal(member_public_key, context_info, associated_data, plaintext)
628 .await
629 .map_err(|e| MlsError::CryptoProviderError(e.into_any_error()))?;
630 Ok(hpke_ciphertext)
631 }
632
633 #[cfg(feature = "non_domain_separated_hpke_encrypt_decrypt")]
640 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
641 #[cfg_attr(feature = "ffi", safer_ffi_gen::safer_ffi_gen_ignore)]
642 pub async fn hpke_encrypt_to_recipient(
643 &self,
644 recipient_index: u32,
645 context_info: &[u8],
646 associated_data: Option<&[u8]>,
647 plaintext: &[u8],
648 ) -> Result<HpkeCiphertext, MlsError> {
649 self.hpke_encrypt_to_recipient_with_generic_context(
650 recipient_index,
651 context_info,
652 associated_data,
653 plaintext,
654 )
655 .await
656 }
657
658 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
665 pub async fn safe_encrypt_with_context_to_recipient(
666 &self,
667 recipient_index: u32,
668 component_id: ComponentID,
669 context: &[u8],
670 associated_data: Option<&[u8]>,
671 plaintext: &[u8],
672 ) -> Result<HpkeCiphertext, MlsError> {
673 let component_operation_label = ComponentOperationLabel::new(component_id, context);
674 self.hpke_encrypt_to_recipient_with_generic_context(
675 recipient_index,
676 &component_operation_label.get_bytes()?,
677 associated_data,
678 plaintext,
679 )
680 .await
681 }
682
683 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
684 async fn hpke_decrypt_for_current_member_with_generic_context(
685 &self,
686 context_info: &[u8],
687 associated_data: Option<&[u8]>,
688 hpke_ciphertext: HpkeCiphertext,
689 ) -> Result<Vec<u8>, MlsError> {
690 let self_private_key = &self.private_tree.secret_keys[0]
691 .as_ref()
692 .ok_or(MlsError::InvalidTreeKemPrivateKey)?;
693 let self_public_key = &self.current_user_leaf_node()?.public_key;
694 let plaintext = self
695 .cipher_suite_provider
696 .hpke_open(
697 &hpke_ciphertext,
698 self_private_key,
699 self_public_key,
700 context_info,
701 associated_data,
702 )
703 .await
704 .map_err(|e| MlsError::CryptoProviderError(e.into_any_error()))?;
705 Ok(plaintext)
706 }
707
708 #[cfg(feature = "non_domain_separated_hpke_encrypt_decrypt")]
715 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
716 #[cfg_attr(feature = "ffi", safer_ffi_gen::safer_ffi_gen_ignore)]
717 pub async fn hpke_decrypt_for_current_member(
718 &self,
719 context_info: &[u8],
720 associated_data: Option<&[u8]>,
721 hpke_ciphertext: HpkeCiphertext,
722 ) -> Result<Vec<u8>, MlsError> {
723 self.hpke_decrypt_for_current_member_with_generic_context(
724 context_info,
725 associated_data,
726 hpke_ciphertext,
727 )
728 .await
729 }
730
731 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
736 pub async fn safe_decrypt_with_context_for_current_member(
737 &self,
738 component_id: ComponentID,
739 context: &[u8],
740 associated_data: Option<&[u8]>,
741 hpke_ciphertext: HpkeCiphertext,
742 ) -> Result<Vec<u8>, MlsError> {
743 let component_operation_label = ComponentOperationLabel::new(component_id, context);
744 self.hpke_decrypt_for_current_member_with_generic_context(
745 &component_operation_label.get_bytes()?,
746 associated_data,
747 hpke_ciphertext,
748 )
749 .await
750 }
751
752 #[inline(always)]
757 pub fn current_member_index(&self) -> u32 {
758 *self.current_member_leaf_index()
759 }
760
761 #[inline(always)]
762 fn current_member_leaf_index(&self) -> LeafIndex {
763 self.private_tree.self_index
764 }
765
766 fn current_user_leaf_node(&self) -> Result<&LeafNode, MlsError> {
767 self.current_epoch_tree()
768 .get_leaf_node(self.private_tree.self_index)
769 }
770
771 #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen_ignore)]
773 pub fn current_member_signing_identity(&self) -> Result<&SigningIdentity, MlsError> {
774 self.current_user_leaf_node().map(|ln| &ln.signing_identity)
775 }
776
777 pub fn member_at_index(&self, index: u32) -> Option<Member> {
782 self.group_state().member_at_index(index)
783 }
784
785 #[cfg(feature = "by_ref_proposal")]
786 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
787 async fn proposal_message(
788 &mut self,
789 proposal: Proposal,
790 authenticated_data: Vec<u8>,
791 ) -> Result<MlsMessage, MlsError> {
792 let sender = Sender::Member(*self.private_tree.self_index);
793
794 let auth_content = AuthenticatedContent::new_signed(
795 &self.cipher_suite_provider,
796 self.context(),
797 sender,
798 Content::Proposal(alloc::boxed::Box::new(proposal.clone())),
799 &self.signer,
800 #[cfg(feature = "private_message")]
801 self.encryption_options()?.control_wire_format(sender),
802 #[cfg(not(feature = "private_message"))]
803 WireFormat::PublicMessage,
804 authenticated_data,
805 )
806 .await?;
807
808 let sender = auth_content.content.sender;
809
810 let proposal_desc =
811 ProposalMessageDescription::new(&self.cipher_suite_provider, &auth_content, proposal)
812 .await?;
813
814 let message = self.format_for_wire(auth_content).await?;
815
816 self.state
817 .proposals
818 .insert_own(proposal_desc, &message, sender, &self.cipher_suite_provider)
819 .await?;
820
821 Ok(message)
822 }
823
824 pub fn group_id(&self) -> &[u8] {
826 &self.context().group_id
827 }
828
829 fn provisional_private_tree(
830 &self,
831 provisional_state: &ProvisionalState,
832 ) -> Result<(TreeKemPrivate, Option<SignatureSecretKey>), MlsError> {
833 let mut provisional_private_tree = self.private_tree.clone();
834 let self_index = provisional_private_tree.self_index;
835
836 let path = provisional_state
838 .public_tree
839 .nodes
840 .direct_copath(self_index);
841
842 provisional_private_tree
843 .secret_keys
844 .resize(path.len() + 1, None);
845
846 for (i, n) in path.iter().enumerate() {
847 if provisional_state.public_tree.nodes.is_blank(n.path)? {
848 provisional_private_tree.secret_keys[i + 1] = None;
849 }
850 }
851
852 let new_signer = None;
854
855 #[cfg(feature = "by_ref_proposal")]
856 let mut new_signer = new_signer;
857
858 #[cfg(feature = "by_ref_proposal")]
859 for p in &provisional_state.applied_proposals.updates {
860 if p.sender == Sender::Member(*self_index) {
861 let leaf_pk = &p.proposal.leaf_node.public_key;
862
863 #[cfg(feature = "std")]
865 let new_leaf_sk_and_signer = self.pending_updates.get(leaf_pk);
866
867 #[cfg(not(feature = "std"))]
868 let new_leaf_sk_and_signer = self
869 .pending_updates
870 .iter()
871 .find_map(|(pk, sk)| (pk == leaf_pk).then_some(sk));
872
873 let new_leaf_sk = new_leaf_sk_and_signer.map(|(sk, _)| sk.clone());
874 new_signer = new_leaf_sk_and_signer.and_then(|(_, sk)| sk.clone());
875
876 provisional_private_tree
877 .update_leaf(new_leaf_sk.ok_or(MlsError::UpdateErrorNoSecretKey)?);
878
879 break;
880 }
881 }
882
883 Ok((provisional_private_tree, new_signer))
884 }
885
886 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
887 async fn encrypt_group_secrets(
888 &self,
889 key_package: &KeyPackage,
890 leaf_index: LeafIndex,
891 joiner_secret: &JoinerSecret,
892 path_secrets: Option<&Vec<Option<PathSecret>>>,
893 #[cfg(feature = "psk")] psks: Vec<PreSharedKeyID>,
894 encrypted_group_info: &[u8],
895 ) -> Result<EncryptedGroupSecrets, MlsError> {
896 let path_secret = path_secrets
897 .map(|secrets| {
898 secrets
899 .get(
900 tree_math::leaf_lca_level(*self.private_tree.self_index, *leaf_index)
901 as usize
902 - 1,
903 )
904 .cloned()
905 .flatten()
906 .ok_or(MlsError::InvalidTreeKemPrivateKey)
907 })
908 .transpose()?;
909
910 #[cfg(not(feature = "psk"))]
911 let psks = Vec::new();
912
913 let group_secrets = GroupSecrets {
914 joiner_secret: joiner_secret.clone(),
915 path_secret,
916 psks,
917 };
918
919 let encrypted_group_secrets = group_secrets
920 .encrypt(
921 &self.cipher_suite_provider,
922 &key_package.hpke_init_key,
923 encrypted_group_info,
924 )
925 .await?;
926
927 Ok(EncryptedGroupSecrets {
928 new_member: key_package
929 .to_reference(&self.cipher_suite_provider)
930 .await?,
931 encrypted_group_secrets,
932 })
933 }
934
935 #[cfg(feature = "by_ref_proposal")]
940 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
941 pub async fn propose_add(
942 &mut self,
943 key_package: MlsMessage,
944 authenticated_data: Vec<u8>,
945 ) -> Result<MlsMessage, MlsError> {
946 let proposal = self.add_proposal(key_package)?;
947 self.proposal_message(proposal, authenticated_data).await
948 }
949
950 fn add_proposal(&self, key_package: MlsMessage) -> Result<Proposal, MlsError> {
951 Ok(Proposal::Add(alloc::boxed::Box::new(AddProposal {
952 key_package: key_package
953 .into_key_package()
954 .ok_or(MlsError::UnexpectedMessageType)?,
955 })))
956 }
957
958 #[cfg(feature = "by_ref_proposal")]
967 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
968 pub async fn propose_update(
969 &mut self,
970 authenticated_data: Vec<u8>,
971 ) -> Result<MlsMessage, MlsError> {
972 let proposal = self.update_proposal(None, None, None).await?;
973 self.proposal_message(proposal, authenticated_data).await
974 }
975
976 #[cfg(feature = "by_ref_proposal")]
994 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
995 pub async fn propose_update_with_identity(
996 &mut self,
997 signer: SignatureSecretKey,
998 signing_identity: SigningIdentity,
999 authenticated_data: Vec<u8>,
1000 ) -> Result<MlsMessage, MlsError> {
1001 let proposal = self
1002 .update_proposal(Some(signer), Some(signing_identity), None)
1003 .await?;
1004
1005 self.proposal_message(proposal, authenticated_data).await
1006 }
1007
1008 #[cfg(feature = "by_ref_proposal")]
1009 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1010 async fn update_proposal(
1011 &mut self,
1012 signer: Option<SignatureSecretKey>,
1013 signing_identity: Option<SigningIdentity>,
1014 leaf_node_extensions: Option<ExtensionList>,
1015 ) -> Result<Proposal, MlsError> {
1016 let mut new_leaf_node: LeafNode = self.current_user_leaf_node()?.clone();
1018
1019 let new_leaf_node_extensions =
1020 leaf_node_extensions.unwrap_or(new_leaf_node.ungreased_extensions());
1021 let secret_key = new_leaf_node
1022 .update(
1023 &self.cipher_suite_provider,
1024 self.group_id(),
1025 self.current_member_index(),
1026 Some(self.config.leaf_properties(new_leaf_node_extensions)),
1027 signing_identity,
1028 signer.as_ref().unwrap_or(&self.signer),
1029 )
1030 .await?;
1031
1032 #[cfg(feature = "std")]
1034 self.pending_updates
1035 .insert(new_leaf_node.public_key.clone(), (secret_key, signer));
1036
1037 #[cfg(not(feature = "std"))]
1038 self.pending_updates
1039 .push((new_leaf_node.public_key.clone(), (secret_key, signer)));
1040
1041 Ok(Proposal::Update(UpdateProposal {
1042 leaf_node: new_leaf_node,
1043 }))
1044 }
1045
1046 #[cfg(feature = "by_ref_proposal")]
1052 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1053 pub async fn propose_remove(
1054 &mut self,
1055 index: u32,
1056 authenticated_data: Vec<u8>,
1057 ) -> Result<MlsMessage, MlsError> {
1058 let proposal = self.remove_proposal(index)?;
1059 self.proposal_message(proposal, authenticated_data).await
1060 }
1061
1062 fn remove_proposal(&self, index: u32) -> Result<Proposal, MlsError> {
1063 let leaf_index = LeafIndex::try_from(index)?;
1064
1065 self.current_epoch_tree().get_leaf_node(leaf_index)?;
1067
1068 Ok(Proposal::Remove(RemoveProposal {
1069 to_remove: leaf_index,
1070 }))
1071 }
1072
1073 #[cfg(all(
1074 feature = "by_ref_proposal",
1075 feature = "custom_proposal",
1076 feature = "self_remove_proposal"
1077 ))]
1078 #[cfg_attr(feature = "ffi", safer_ffi_gen::safer_ffi_gen_ignore)]
1079 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1080 pub async fn propose_self_remove(
1081 &mut self,
1082 authenticated_data: Vec<u8>,
1083 ) -> Result<MlsMessage, MlsError> {
1084 if self.state.proposals.has_own_self_remove() {
1085 return Err(MlsError::SelfRemoveAlreadyProposed);
1086 }
1087 let proposal = Proposal::SelfRemove(SelfRemoveProposal {});
1088 self.proposal_message(proposal, authenticated_data).await
1089 }
1090
1091 #[cfg(all(feature = "by_ref_proposal", feature = "psk"))]
1102 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1103 pub async fn propose_external_psk(
1104 &mut self,
1105 psk: ExternalPskId,
1106 authenticated_data: Vec<u8>,
1107 ) -> Result<MlsMessage, MlsError> {
1108 let proposal = self.psk_proposal(JustPreSharedKeyID::External(psk))?;
1109 self.proposal_message(proposal, authenticated_data).await
1110 }
1111
1112 #[cfg(feature = "psk")]
1113 fn psk_proposal(&self, key_id: JustPreSharedKeyID) -> Result<Proposal, MlsError> {
1114 Ok(Proposal::Psk(PreSharedKeyProposal {
1115 psk: PreSharedKeyID::new(key_id, &self.cipher_suite_provider)?,
1116 }))
1117 }
1118
1119 #[cfg(all(feature = "by_ref_proposal", feature = "psk"))]
1129 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1130 pub async fn propose_resumption_psk(
1131 &mut self,
1132 psk_epoch: u64,
1133 authenticated_data: Vec<u8>,
1134 ) -> Result<MlsMessage, MlsError> {
1135 let key_id = ResumptionPsk {
1136 psk_epoch,
1137 usage: ResumptionPSKUsage::Application,
1138 psk_group_id: PskGroupId(self.group_id().to_vec()),
1139 };
1140
1141 let proposal = self.psk_proposal(JustPreSharedKeyID::Resumption(key_id))?;
1142 self.proposal_message(proposal, authenticated_data).await
1143 }
1144
1145 #[cfg(feature = "by_ref_proposal")]
1155 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1156 pub async fn propose_reinit(
1157 &mut self,
1158 group_id: Option<Vec<u8>>,
1159 version: ProtocolVersion,
1160 cipher_suite: CipherSuite,
1161 extensions: ExtensionList,
1162 authenticated_data: Vec<u8>,
1163 ) -> Result<MlsMessage, MlsError> {
1164 let proposal = self.reinit_proposal(group_id, version, cipher_suite, extensions)?;
1165 self.proposal_message(proposal, authenticated_data).await
1166 }
1167
1168 fn reinit_proposal(
1169 &self,
1170 group_id: Option<Vec<u8>>,
1171 version: ProtocolVersion,
1172 cipher_suite: CipherSuite,
1173 extensions: ExtensionList,
1174 ) -> Result<Proposal, MlsError> {
1175 let group_id = group_id.map(Ok).unwrap_or_else(|| {
1176 self.cipher_suite_provider
1177 .random_bytes_vec(self.cipher_suite_provider.kdf_extract_size())
1178 .map_err(|e| MlsError::CryptoProviderError(e.into_any_error()))
1179 })?;
1180
1181 Ok(Proposal::ReInit(ReInitProposal {
1182 group_id,
1183 version,
1184 cipher_suite,
1185 extensions,
1186 }))
1187 }
1188
1189 #[cfg(feature = "by_ref_proposal")]
1203 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1204 pub async fn propose_group_context_extensions(
1205 &mut self,
1206 extensions: ExtensionList,
1207 authenticated_data: Vec<u8>,
1208 ) -> Result<MlsMessage, MlsError> {
1209 let proposal = self.group_context_extensions_proposal(extensions);
1210 self.proposal_message(proposal, authenticated_data).await
1211 }
1212
1213 fn group_context_extensions_proposal(&self, extensions: ExtensionList) -> Proposal {
1214 Proposal::GroupContextExtensions(extensions)
1215 }
1216
1217 #[cfg(all(feature = "custom_proposal", feature = "by_ref_proposal"))]
1222 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1223 pub async fn propose_custom(
1224 &mut self,
1225 proposal: CustomProposal,
1226 authenticated_data: Vec<u8>,
1227 ) -> Result<MlsMessage, MlsError> {
1228 self.proposal_message(Proposal::Custom(proposal), authenticated_data)
1229 .await
1230 }
1231
1232 #[cfg(all(
1245 feature = "custom_proposal",
1246 feature = "by_ref_proposal",
1247 feature = "prior_epoch_membership_key"
1248 ))]
1249 #[cfg_attr(feature = "ffi", safer_ffi_gen::safer_ffi_gen_ignore)]
1250 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1251 pub async fn validate_custom_proposal(
1252 &mut self,
1253 msg: &MlsMessage,
1254 proposal_type: Option<ProposalType>,
1255 ) -> Result<(Vec<u8>, Sender), MlsError> {
1256 let auth_content = self.validate_public_message(msg).await?;
1257 let proposal = match auth_content.content.content {
1258 Content::Proposal(p) => match *p {
1259 Proposal::Custom(c) => c,
1260 _ => return Err(MlsError::UnexpectedMessageType),
1261 },
1262 _ => return Err(MlsError::UnexpectedMessageType),
1263 };
1264 if proposal_type.is_some() && Some(proposal.proposal_type()) != proposal_type {
1265 return Err(MlsError::UnsupportedCustomProposal(
1266 proposal.proposal_type(),
1267 ));
1268 }
1269 Ok((
1270 auth_content.content.authenticated_data,
1271 auth_content.content.sender,
1272 ))
1273 }
1274
1275 #[cfg(all(feature = "custom_proposal", feature = "prior_epoch_membership_key"))]
1278 #[cfg_attr(feature = "ffi", safer_ffi_gen::safer_ffi_gen_ignore)]
1279 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1280 async fn validate_public_message(
1281 &mut self,
1282 msg: &MlsMessage,
1283 ) -> Result<AuthenticatedContent, MlsError> {
1284 let plaintext = match msg.payload {
1285 MlsMessagePayload::Plain(ref plaintext) => plaintext.clone(),
1286 _ => return Err(MlsError::UnexpectedMessageType),
1287 };
1288 let epoch_id = msg.epoch().ok_or(MlsError::EpochNotFound)?;
1289 let auth_content = if epoch_id == self.context().epoch {
1290 let auth_content = verify_plaintext_authentication(
1291 &self.cipher_suite_provider,
1292 plaintext,
1293 Some(&self.key_schedule.membership_key),
1294 &self.state.context,
1295 SignaturePublicKeysContainer::RatchetTree(&self.state.public_tree),
1296 )
1297 .await?;
1298
1299 Ok::<_, MlsError>(auth_content)
1300 } else {
1301 #[cfg(feature = "prior_epoch")]
1302 {
1303 let epoch = self
1304 .state_repo
1305 .get_epoch_mut(epoch_id)
1306 .await?
1307 .ok_or(MlsError::EpochNotFound)?;
1308
1309 let auth_content = verify_plaintext_authentication(
1310 &self.cipher_suite_provider,
1311 plaintext,
1312 Some(&epoch.membership_key),
1313 &epoch.context,
1314 SignaturePublicKeysContainer::List(&epoch.signature_public_keys),
1315 )
1316 .await?;
1317
1318 Ok(auth_content)
1319 }
1320
1321 #[cfg(not(feature = "prior_epoch"))]
1322 Err(MlsError::EpochNotFound)
1323 }?;
1324
1325 Ok(auth_content)
1326 }
1327
1328 #[cfg(feature = "by_ref_proposal")]
1330 pub fn clear_proposal_cache(&mut self) {
1331 self.state.proposals.clear()
1332 }
1333
1334 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1335 pub(crate) async fn format_for_wire(
1336 &mut self,
1337 content: AuthenticatedContent,
1338 ) -> Result<MlsMessage, MlsError> {
1339 #[cfg(feature = "private_message")]
1340 let payload = if content.wire_format == WireFormat::PrivateMessage {
1341 MlsMessagePayload::Cipher(self.create_ciphertext(content).await?)
1342 } else {
1343 MlsMessagePayload::Plain(self.create_plaintext(content).await?)
1344 };
1345 #[cfg(not(feature = "private_message"))]
1346 let payload = MlsMessagePayload::Plain(self.create_plaintext(content).await?);
1347
1348 Ok(MlsMessage::new(self.protocol_version(), payload))
1349 }
1350
1351 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1352 async fn create_plaintext(
1353 &self,
1354 auth_content: AuthenticatedContent,
1355 ) -> Result<PublicMessage, MlsError> {
1356 let membership_tag = if matches!(auth_content.content.sender, Sender::Member(_)) {
1357 let tag = self
1358 .key_schedule
1359 .get_membership_tag(&auth_content, self.context(), &self.cipher_suite_provider)
1360 .await?;
1361
1362 Some(tag)
1363 } else {
1364 None
1365 };
1366
1367 Ok(PublicMessage {
1368 content: auth_content.content,
1369 auth: auth_content.auth,
1370 membership_tag,
1371 })
1372 }
1373
1374 #[cfg(feature = "private_message")]
1375 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1376 async fn create_ciphertext(
1377 &mut self,
1378 auth_content: AuthenticatedContent,
1379 ) -> Result<PrivateMessage, MlsError> {
1380 let padding_mode = self.encryption_options()?.padding_mode;
1381
1382 let mut encryptor = CiphertextProcessor::new(self, self.cipher_suite_provider.clone());
1383
1384 encryptor.seal(auth_content, padding_mode).await
1385 }
1386
1387 #[cfg(feature = "private_message")]
1392 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1393 pub async fn encrypt_application_message(
1394 &mut self,
1395 message: &[u8],
1396 authenticated_data: Vec<u8>,
1397 ) -> Result<MlsMessage, MlsError> {
1398 #[cfg(feature = "by_ref_proposal")]
1401 if !self.state.proposals.is_empty() {
1402 return Err(MlsError::CommitRequired);
1403 }
1404
1405 let auth_content = AuthenticatedContent::new_signed(
1406 &self.cipher_suite_provider,
1407 self.context(),
1408 Sender::Member(*self.private_tree.self_index),
1409 Content::Application(message.to_vec().into()),
1410 &self.signer,
1411 WireFormat::PrivateMessage,
1412 authenticated_data,
1413 )
1414 .await?;
1415
1416 self.format_for_wire(auth_content).await
1417 }
1418
1419 #[cfg(feature = "private_message")]
1420 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1421 async fn decrypt_incoming_ciphertext(
1422 &mut self,
1423 message: &PrivateMessage,
1424 ) -> Result<AuthenticatedContent, MlsError> {
1425 let epoch_id = message.epoch;
1426
1427 let auth_content = if epoch_id == self.context().epoch {
1428 let content = CiphertextProcessor::new(self, self.cipher_suite_provider.clone())
1429 .open(message)
1430 .await?;
1431
1432 verify_auth_content_signature(
1433 &self.cipher_suite_provider,
1434 SignaturePublicKeysContainer::RatchetTree(&self.state.public_tree),
1435 self.context(),
1436 &content,
1437 #[cfg(feature = "by_ref_proposal")]
1438 &[],
1439 )
1440 .await?;
1441
1442 Ok::<_, MlsError>(content)
1443 } else {
1444 #[cfg(feature = "prior_epoch")]
1445 {
1446 let epoch = self
1447 .state_repo
1448 .get_epoch_mut(epoch_id)
1449 .await?
1450 .ok_or(MlsError::EpochNotFound)?;
1451
1452 let content = CiphertextProcessor::new(epoch, self.cipher_suite_provider.clone())
1453 .open(message)
1454 .await?;
1455
1456 verify_auth_content_signature(
1457 &self.cipher_suite_provider,
1458 SignaturePublicKeysContainer::List(&epoch.signature_public_keys),
1459 &epoch.context,
1460 &content,
1461 #[cfg(feature = "by_ref_proposal")]
1462 &[],
1463 )
1464 .await?;
1465
1466 Ok(content)
1467 }
1468
1469 #[cfg(not(feature = "prior_epoch"))]
1470 Err(MlsError::EpochNotFound)
1471 }?;
1472
1473 Ok(auth_content)
1474 }
1475
1476 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1479 pub async fn apply_pending_commit(&mut self) -> Result<CommitMessageDescription, MlsError> {
1480 let pending = core::mem::take(&mut self.pending_commit);
1481 self.apply_detached_commit(CommitSecrets(pending)).await
1482 }
1483
1484 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1485 pub async fn apply_pending_commit_backwards_compatible(
1486 &mut self,
1487 ) -> Result<CommitMessageDescription, MlsError> {
1488 let pending = core::mem::take(&mut self.pending_commit);
1489 self.apply_detached_commit_backwards_compatible(CommitSecrets(pending))
1490 .await
1491 }
1492
1493 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1496 pub async fn apply_detached_commit(
1497 &mut self,
1498 commit_secrets: CommitSecrets,
1499 ) -> Result<CommitMessageDescription, MlsError> {
1500 let pending = match commit_secrets.0 {
1501 PendingCommitSnapshot::PendingCommit(bytes) => PendingCommit::mls_decode(&mut &*bytes)?,
1502 _ => return Err(MlsError::PendingCommitNotFound),
1503 };
1504
1505 self.insert_past_epoch().await?;
1506
1507 self.state = pending.state;
1508 self.epoch_secrets = pending.epoch_secrets;
1509 self.private_tree = pending.private_tree;
1510 self.key_schedule = pending.key_schedule;
1511 self.signer = pending.signer;
1512
1513 Ok(pending.output)
1514 }
1515
1516 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1517 pub async fn apply_detached_commit_backwards_compatible(
1518 &mut self,
1519 commit_secrets: CommitSecrets,
1520 ) -> Result<CommitMessageDescription, MlsError> {
1521 match commit_secrets.0 {
1522 PendingCommitSnapshot::None | PendingCommitSnapshot::PendingCommit(_) => {
1523 self.apply_detached_commit(commit_secrets).await
1524 }
1525 PendingCommitSnapshot::LegacyPendingCommit(legacy_pending) => {
1526 let content = legacy_pending.content.clone();
1527 self.pending_commit = PendingCommitSnapshot::LegacyPendingCommit(legacy_pending);
1528 self.process_commit(content, None).await
1529 }
1530 }
1531 }
1532
1533 pub fn has_pending_commit(&self) -> bool {
1536 !self.pending_commit.is_none()
1537 }
1538
1539 pub fn clear_pending_commit(&mut self) {
1545 self.pending_commit = Default::default()
1546 }
1547
1548 #[cfg(feature = "by_ref_proposal")]
1552 pub fn commit_required(&self) -> bool {
1553 !self.state.proposals.is_empty()
1554 }
1555
1556 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1565 #[inline(never)]
1566 pub async fn process_incoming_message(
1567 &mut self,
1568 message: MlsMessage,
1569 ) -> Result<ReceivedMessage, MlsError> {
1570 if let Some(pending) = self.pending_commit.commit_hash()? {
1571 let message_hash = MessageHash::compute(&self.cipher_suite_provider, &message).await?;
1572
1573 if message_hash == pending {
1574 let message_description = self.apply_pending_commit().await?;
1575
1576 return Ok(ReceivedMessage::Commit(message_description));
1577 }
1578 }
1579
1580 #[cfg(feature = "by_ref_proposal")]
1581 if message.wire_format() == WireFormat::PrivateMessage {
1582 let cached_own_proposal = self
1583 .state
1584 .proposals
1585 .get_own(&self.cipher_suite_provider, &message)
1586 .await?;
1587
1588 if let Some(cached) = cached_own_proposal {
1589 return Ok(ReceivedMessage::Proposal(cached));
1590 }
1591 }
1592
1593 MessageProcessor::process_incoming_message(
1594 self,
1595 message,
1596 #[cfg(feature = "by_ref_proposal")]
1597 true,
1598 )
1599 .await
1600 }
1601
1602 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1619 pub async fn process_incoming_message_with_time(
1620 &mut self,
1621 message: MlsMessage,
1622 time: MlsTime,
1623 ) -> Result<ReceivedMessage, MlsError> {
1624 if let Some(pending) = self.pending_commit.commit_hash()? {
1625 let message_hash = MessageHash::compute(&self.cipher_suite_provider, &message).await?;
1626
1627 if message_hash == pending {
1628 let message_description = self.apply_pending_commit().await?;
1629
1630 return Ok(ReceivedMessage::Commit(message_description));
1631 }
1632 }
1633
1634 #[cfg(feature = "by_ref_proposal")]
1635 if message.wire_format() == WireFormat::PrivateMessage {
1636 let cached_own_proposal = self
1637 .state
1638 .proposals
1639 .get_own(&self.cipher_suite_provider, &message)
1640 .await?;
1641
1642 if let Some(cached) = cached_own_proposal {
1643 return Ok(ReceivedMessage::Proposal(cached));
1644 }
1645 }
1646
1647 MessageProcessor::process_incoming_message_with_time(
1648 self,
1649 message,
1650 #[cfg(feature = "by_ref_proposal")]
1651 true,
1652 Some(time),
1653 )
1654 .await
1655 }
1656
1657 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1664 pub async fn member_with_identity(&self, identity: &[u8]) -> Result<Member, MlsError> {
1665 let tree = &self.state.public_tree;
1666
1667 #[cfg(feature = "tree_index")]
1668 let index = tree.get_leaf_node_with_identity(identity);
1669
1670 #[cfg(not(feature = "tree_index"))]
1671 let index = tree
1672 .get_leaf_node_with_identity(
1673 identity,
1674 &self.identity_provider(),
1675 &self.state.context.extensions,
1676 )
1677 .await?;
1678
1679 let index = index.ok_or(MlsError::MemberNotFound)?;
1680 let node = self.state.public_tree.get_leaf_node(index)?;
1681
1682 Ok(member_from_leaf_node(node, index))
1683 }
1684
1685 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1693 pub async fn group_info_message_allowing_ext_commit(
1694 &self,
1695 with_tree_in_extension: bool,
1696 ) -> Result<MlsMessage, MlsError> {
1697 let mut extensions = ExtensionList::new();
1698
1699 extensions.set_from({
1700 self.key_schedule
1701 .get_external_key_pair_ext(&self.cipher_suite_provider)
1702 .await?
1703 })?;
1704
1705 self.group_info_message_internal(extensions, with_tree_in_extension)
1706 .await
1707 }
1708
1709 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1711 pub async fn group_info_message(
1712 &self,
1713 with_tree_in_extension: bool,
1714 ) -> Result<MlsMessage, MlsError> {
1715 self.group_info_message_internal(ExtensionList::new(), with_tree_in_extension)
1716 .await
1717 }
1718
1719 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1720 pub async fn group_info_message_internal(
1721 &self,
1722 mut initial_extensions: ExtensionList,
1723 with_tree_in_extension: bool,
1724 ) -> Result<MlsMessage, MlsError> {
1725 if with_tree_in_extension {
1726 initial_extensions.set_from(RatchetTreeExt {
1727 tree_data: ExportedTree::new(self.state.public_tree.nodes.clone()),
1728 })?;
1729 }
1730
1731 let mut info = GroupInfo {
1732 group_context: self.context().clone(),
1733 extensions: initial_extensions,
1734 confirmation_tag: self.state.confirmation_tag.clone(),
1735 signer: self.private_tree.self_index,
1736 signature: Vec::new(),
1737 };
1738
1739 info.grease(self.cipher_suite_provider())?;
1740
1741 info.sign(&self.cipher_suite_provider, &self.signer, &())
1742 .await?;
1743
1744 Ok(MlsMessage::new(
1745 self.protocol_version(),
1746 MlsMessagePayload::GroupInfo(info),
1747 ))
1748 }
1749
1750 #[inline(always)]
1752 pub fn context(&self) -> &GroupContext {
1753 &self.group_state().context
1754 }
1755
1756 pub fn epoch_authenticator(&self) -> Result<Secret, MlsError> {
1760 Ok(self.key_schedule.authentication_secret.clone().into())
1761 }
1762
1763 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1769 pub async fn export_secret(
1770 &self,
1771 label: &[u8],
1772 context: &[u8],
1773 len: usize,
1774 ) -> Result<Secret, MlsError> {
1775 self.key_schedule
1776 .export_secret(label, context, len, &self.cipher_suite_provider)
1777 .await
1778 .map(Into::into)
1779 }
1780
1781 pub fn delete_exporter(&mut self) {
1787 self.key_schedule.delete_exporter();
1788 }
1789
1790 pub fn export_tree(&self) -> ExportedTree<'_> {
1795 ExportedTree::new_borrowed(&self.current_epoch_tree().nodes)
1796 }
1797
1798 pub fn protocol_version(&self) -> ProtocolVersion {
1800 self.context().protocol_version
1801 }
1802
1803 pub fn cipher_suite(&self) -> CipherSuite {
1805 self.context().cipher_suite
1806 }
1807
1808 pub fn roster(&self) -> Roster<'_> {
1810 self.group_state().public_tree.roster()
1811 }
1812
1813 pub fn equal_group_state(a: &Group<C>, b: &Group<C>) -> bool {
1817 a.state == b.state && a.key_schedule == b.key_schedule && a.epoch_secrets == b.epoch_secrets
1818 }
1819
1820 #[cfg(feature = "psk")]
1821 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1822 async fn get_psk(
1823 &self,
1824 psks: &[ProposalInfo<PreSharedKeyProposal>],
1825 ) -> Result<(PskSecret, Vec<PreSharedKeyID>), MlsError> {
1826 if let Some(psk) = self.previous_psk.clone() {
1827 let psk_id = vec![psk.id.clone()];
1829 let psk = PskSecret::calculate(&[psk], self.cipher_suite_provider()).await?;
1830
1831 Ok((psk, psk_id))
1832 } else {
1833 let psks = psks
1834 .iter()
1835 .map(|psk| psk.proposal.psk.clone())
1836 .collect::<Vec<_>>();
1837
1838 let psk = PskResolver {
1839 group_context: Some(self.context()),
1840 current_epoch: Some(&self.epoch_secrets),
1841 prior_epochs: Some(&self.state_repo),
1842 psk_store: &self.config.secret_store(),
1843 }
1844 .resolve_to_secret(&psks, self.cipher_suite_provider())
1845 .await?;
1846
1847 Ok((psk, psks))
1848 }
1849 }
1850
1851 #[cfg(feature = "private_message")]
1852 pub(crate) fn encryption_options(&self) -> Result<EncryptionOptions, MlsError> {
1853 self.config
1854 .mls_rules()
1855 .encryption_options(&self.roster(), self.group_context())
1856 .map_err(|e| MlsError::MlsRulesError(e.into_any_error()))
1857 }
1858
1859 #[cfg(not(feature = "psk"))]
1860 fn get_psk(&self) -> PskSecret {
1861 PskSecret::new(self.cipher_suite_provider())
1862 }
1863
1864 #[cfg(all(
1881 feature = "export_key_generation",
1882 feature = "private_message",
1883 feature = "secret_tree_access",
1884 ))]
1885 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1886 pub fn peek_next_key_generation(&mut self) -> Result<u32, MlsError> {
1887 self.epoch_secrets
1888 .secret_tree
1889 .peek_next_key_generation(
1890 &self.cipher_suite_provider,
1891 crate::tree_kem::node::NodeIndex::from(self.private_tree.self_index),
1892 KeyType::Application,
1893 )
1894 .await
1895 }
1896
1897 #[cfg(feature = "secret_tree_access")]
1898 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1899 #[inline(never)]
1900 pub async fn next_encryption_key(&mut self) -> Result<MessageKey, MlsError> {
1901 self.epoch_secrets
1902 .secret_tree
1903 .next_message_key(
1904 &self.cipher_suite_provider,
1905 crate::tree_kem::node::NodeIndex::from(self.private_tree.self_index),
1906 KeyType::Application,
1907 )
1908 .await
1909 }
1910
1911 #[cfg(feature = "secret_tree_access")]
1912 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1913 pub async fn derive_decryption_key(
1914 &mut self,
1915 sender: u32,
1916 generation: u32,
1917 ) -> Result<MessageKey, MlsError> {
1918 self.epoch_secrets
1919 .secret_tree
1920 .message_key_generation(
1921 &self.cipher_suite_provider,
1922 crate::tree_kem::node::NodeIndex::from(sender),
1923 KeyType::Application,
1924 generation,
1925 )
1926 .await
1927 }
1928}
1929
1930impl<C: ClientConfig> Group<C> {
1931 #[cfg(feature = "psk")]
1932 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1933 async fn psk_secret<CS: CipherSuiteProvider>(
1934 config: &C,
1935 cipher_suite_provider: &CS,
1936 psks: &[PreSharedKeyID],
1937 additional_psk: Option<PskSecretInput>,
1938 ) -> Result<PskSecret, MlsError> {
1939 if let Some(psk) = additional_psk {
1940 let psk_id = psks.first().ok_or(MlsError::UnexpectedPskId)?;
1941
1942 match &psk_id.key_id {
1943 JustPreSharedKeyID::Resumption(r) if r.usage != ResumptionPSKUsage::Application => {
1944 Ok(())
1945 }
1946 _ => Err(MlsError::UnexpectedPskId),
1947 }?;
1948
1949 let mut psk = psk;
1950 psk.id.psk_nonce = psk_id.psk_nonce.clone();
1951 PskSecret::calculate(&[psk], cipher_suite_provider).await
1952 } else {
1953 PskResolver::<
1954 <C as ClientConfig>::GroupStateStorage,
1955 <C as ClientConfig>::KeyPackageRepository,
1956 <C as ClientConfig>::PskStore,
1957 > {
1958 group_context: None,
1959 current_epoch: None,
1960 prior_epochs: None,
1961 psk_store: &config.secret_store(),
1962 }
1963 .resolve_to_secret(psks, cipher_suite_provider)
1964 .await
1965 }
1966 }
1967
1968 #[cfg(not(feature = "psk"))]
1969 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1970 async fn psk_secret<CS: CipherSuiteProvider>(
1971 _config: &C,
1972 cipher_suite_provider: &CS,
1973 _psks: &[PreSharedKeyID],
1974 ) -> Result<PskSecret, MlsError> {
1975 Ok(PskSecret::new(cipher_suite_provider))
1976 }
1977
1978 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1979 pub(crate) async fn decrypt_group_info(
1980 welcome: &MlsMessage,
1981 config: &C,
1982 ) -> Result<GroupInfo, MlsError> {
1983 Self::decrypt_group_info_internal(
1984 welcome,
1985 config,
1986 #[cfg(feature = "psk")]
1987 None,
1988 )
1989 .await
1990 .map(|info| info.0)
1991 }
1992
1993 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1994 async fn decrypt_group_info_internal(
1995 welcome: &MlsMessage,
1996 config: &C,
1997 #[cfg(feature = "psk")] additional_psk: Option<PskSecretInput>,
1998 ) -> Result<(GroupInfo, KeyPackageGeneration, GroupSecrets, PskSecret), MlsError> {
1999 let protocol_version = welcome.version;
2000
2001 if !config.version_supported(protocol_version) {
2002 return Err(MlsError::UnsupportedProtocolVersion(protocol_version));
2003 }
2004
2005 let MlsMessagePayload::Welcome(welcome) = &welcome.payload else {
2006 return Err(MlsError::UnexpectedMessageType);
2007 };
2008
2009 let cipher_suite_provider =
2010 cipher_suite_provider(config.crypto_provider(), welcome.cipher_suite)?;
2011
2012 let (encrypted_group_secrets, key_package_generation) =
2013 find_key_package_generation(&config.key_package_repo(), &welcome.secrets).await?;
2014
2015 let key_package_version = key_package_generation.key_package.version;
2016
2017 if key_package_version != protocol_version {
2018 return Err(MlsError::ProtocolVersionMismatch);
2019 }
2020
2021 let group_secrets = GroupSecrets::decrypt(
2026 &cipher_suite_provider,
2027 &key_package_generation.init_secret_key,
2028 &key_package_generation.key_package.hpke_init_key,
2029 &welcome.encrypted_group_info,
2030 &encrypted_group_secrets.encrypted_group_secrets,
2031 )
2032 .await?;
2033
2034 let psk_secret = Self::psk_secret(
2035 config,
2036 &cipher_suite_provider,
2037 &group_secrets.psks,
2038 #[cfg(feature = "psk")]
2039 additional_psk,
2040 )
2041 .await?;
2042
2043 let welcome_secret = WelcomeSecret::from_joiner_secret(
2047 &cipher_suite_provider,
2048 &group_secrets.joiner_secret,
2049 &psk_secret,
2050 )
2051 .await?;
2052
2053 let decrypted_group_info = welcome_secret
2055 .decrypt(&welcome.encrypted_group_info)
2056 .await?;
2057
2058 let group_info = GroupInfo::mls_decode(&mut &**decrypted_group_info)?;
2059
2060 Ok((
2061 group_info,
2062 key_package_generation,
2063 group_secrets,
2064 psk_secret,
2065 ))
2066 }
2067
2068 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
2069 #[cfg(feature = "prior_epoch")]
2070 pub(crate) async fn insert_past_epoch(&mut self) -> Result<(), MlsError> {
2071 let signature_public_keys = self
2072 .state
2073 .public_tree
2074 .leaves()
2075 .map(|l| l.map(|n| n.signing_identity.signature_key.clone()))
2076 .collect();
2077
2078 let past_epoch = PriorEpoch {
2079 context: self.context().clone(),
2080 self_index: self.private_tree.self_index,
2081 secrets: self.epoch_secrets.clone(),
2082 signature_public_keys,
2083 #[cfg(feature = "prior_epoch_membership_key")]
2084 membership_key: self.key_schedule.membership_key.to_vec(),
2085 };
2086
2087 self.state_repo.insert(past_epoch).await?;
2088
2089 Ok(())
2090 }
2091
2092 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
2093 #[cfg(not(feature = "prior_epoch"))]
2094 pub(crate) async fn insert_past_epoch(&mut self) -> Result<(), MlsError> {
2095 Ok(())
2096 }
2097}
2098
2099#[cfg(feature = "private_message")]
2100impl<C> GroupStateProvider for Group<C>
2101where
2102 C: ClientConfig + Clone,
2103{
2104 fn group_context(&self) -> &GroupContext {
2105 self.context()
2106 }
2107
2108 fn self_index(&self) -> LeafIndex {
2109 self.private_tree.self_index
2110 }
2111
2112 fn epoch_secrets_mut(&mut self) -> &mut EpochSecrets {
2113 &mut self.epoch_secrets
2114 }
2115
2116 fn epoch_secrets(&self) -> &EpochSecrets {
2117 &self.epoch_secrets
2118 }
2119}
2120
2121#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
2122#[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
2123#[cfg_attr(
2124 all(not(target_arch = "wasm32"), mls_build_async),
2125 maybe_async::must_be_async
2126)]
2127impl<C> MessageProcessor for Group<C>
2128where
2129 C: ClientConfig + Clone,
2130{
2131 type MlsRules = C::MlsRules;
2132 type IdentityProvider = C::IdentityProvider;
2133 type PreSharedKeyStorage = C::PskStore;
2134 type OutputType = ReceivedMessage;
2135 type CipherSuiteProvider = <C::CryptoProvider as CryptoProvider>::CipherSuiteProvider;
2136
2137 #[cfg(feature = "private_message")]
2138 async fn process_ciphertext(
2139 &mut self,
2140 cipher_text: &PrivateMessage,
2141 ) -> Result<EventOrContent<Self::OutputType>, MlsError> {
2142 self.decrypt_incoming_ciphertext(cipher_text)
2143 .await
2144 .map(EventOrContent::Content)
2145 }
2146
2147 async fn verify_plaintext_authentication(
2148 &self,
2149 message: PublicMessage,
2150 ) -> Result<EventOrContent<Self::OutputType>, MlsError> {
2151 let auth_content = verify_plaintext_authentication(
2152 &self.cipher_suite_provider,
2153 message,
2154 Some(&self.key_schedule.membership_key),
2155 &self.state.context,
2156 SignaturePublicKeysContainer::RatchetTree(&self.state.public_tree),
2157 )
2158 .await?;
2159
2160 Ok(EventOrContent::Content(auth_content))
2161 }
2162
2163 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
2164 async fn get_unauthenticated_key_generation_from_sender_data(
2166 &mut self,
2167 cipher_text: &PrivateMessage,
2168 ) -> Result<Option<u32>, MlsError> {
2169 let epoch_id = cipher_text.epoch;
2170 let sender_data = if epoch_id == self.context().epoch {
2171 CiphertextProcessor::new(self, self.cipher_suite_provider.clone())
2172 .open_sender_data(cipher_text)
2173 .await?
2174 } else {
2175 #[cfg(feature = "prior_epoch")]
2176 {
2177 let epoch = self
2178 .state_repo
2179 .get_epoch_mut(epoch_id)
2180 .await?
2181 .ok_or(MlsError::EpochNotFound)?;
2182 CiphertextProcessor::new(epoch, self.cipher_suite_provider.clone())
2183 .open_sender_data(cipher_text)
2184 .await?
2185 }
2186 #[cfg(not(feature = "prior_epoch"))]
2187 Err(MlsError::EpochNotFound)
2188 };
2189 Ok(Some(sender_data.generation))
2190 }
2191
2192 async fn apply_update_path(
2193 &mut self,
2194 sender: LeafIndex,
2195 update_path: &ValidatedUpdatePath,
2196 provisional_state: &mut ProvisionalState,
2197 ) -> Result<Option<(TreeKemPrivate, PathSecret)>, MlsError> {
2198 let (mut provisional_private_tree, new_signer) =
2200 self.provisional_private_tree(provisional_state)?;
2201
2202 if let Some(signer) = new_signer {
2203 self.signer = signer;
2204 }
2205
2206 provisional_state
2207 .public_tree
2208 .apply_update_path(
2209 sender,
2210 update_path,
2211 &provisional_state.group_context.extensions,
2212 self.identity_provider(),
2213 self.cipher_suite_provider(),
2214 )
2215 .await?;
2216
2217 if sender == self.private_tree.self_index {
2218 let PendingCommitSnapshot::LegacyPendingCommit(legacy_pending) = &self.pending_commit
2219 else {
2220 return Err(MlsError::CantProcessMessageFromSelf);
2221 };
2222
2223 return Ok(Some((
2224 legacy_pending.private_tree.clone(),
2225 legacy_pending.commit_secret.clone(),
2226 )));
2227 }
2228
2229 provisional_state.group_context.tree_hash = provisional_state
2231 .public_tree
2232 .tree_hash(&self.cipher_suite_provider)
2233 .await?;
2234
2235 let context_bytes = provisional_state.group_context.mls_encode_to_vec()?;
2236
2237 TreeKem::new(
2238 &mut provisional_state.public_tree,
2239 &mut provisional_private_tree,
2240 )
2241 .decap(
2242 sender,
2243 update_path,
2244 &provisional_state.indexes_of_added_kpkgs,
2245 &context_bytes,
2246 &self.cipher_suite_provider,
2247 )
2248 .await
2249 .map(|root_secret| Some((provisional_private_tree, root_secret)))
2250 }
2251
2252 async fn update_key_schedule(
2253 &mut self,
2254 secrets: Option<(TreeKemPrivate, PathSecret)>,
2255 interim_transcript_hash: InterimTranscriptHash,
2256 confirmation_tag: &ConfirmationTag,
2257 provisional_state: ProvisionalState,
2258 ) -> Result<(), MlsError> {
2259 let commit_secret = if let Some(secrets) = secrets {
2260 self.private_tree = secrets.0;
2261 secrets.1
2262 } else {
2263 PathSecret::empty(&self.cipher_suite_provider)
2264 };
2265
2266 let key_schedule = match provisional_state
2271 .applied_proposals
2272 .external_initializations
2273 .first()
2274 {
2275 Some(ext_init) => {
2276 self.key_schedule
2277 .derive_for_external(&ext_init.proposal.kem_output, &self.cipher_suite_provider)
2278 .await?
2279 }
2280 _ => self.key_schedule.clone(),
2281 };
2282
2283 #[cfg(feature = "psk")]
2284 let (psk, _) = self
2285 .get_psk(&provisional_state.applied_proposals.psks)
2286 .await?;
2287
2288 #[cfg(not(feature = "psk"))]
2289 let psk = self.get_psk();
2290
2291 let key_schedule_result = KeySchedule::from_key_schedule(
2292 &key_schedule,
2293 &commit_secret,
2294 &provisional_state.group_context,
2295 #[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
2296 provisional_state.public_tree.total_leaf_count(),
2297 &psk,
2298 &self.cipher_suite_provider,
2299 )
2300 .await?;
2301
2302 let new_confirmation_tag = ConfirmationTag::create(
2306 &key_schedule_result.confirmation_key,
2307 &provisional_state.group_context.confirmed_transcript_hash,
2308 &self.cipher_suite_provider,
2309 )
2310 .await?;
2311
2312 if &new_confirmation_tag != confirmation_tag {
2313 return Err(MlsError::InvalidConfirmationTag);
2314 }
2315
2316 self.insert_past_epoch().await?;
2317
2318 self.epoch_secrets = key_schedule_result.epoch_secrets;
2319 self.state.context = provisional_state.group_context;
2320 self.state.interim_transcript_hash = interim_transcript_hash;
2321 self.key_schedule = key_schedule_result.key_schedule;
2322 self.state.public_tree = provisional_state.public_tree;
2323 self.state.confirmation_tag = new_confirmation_tag;
2324
2325 #[cfg(feature = "by_ref_proposal")]
2327 self.state.proposals.clear();
2328
2329 #[cfg(feature = "by_ref_proposal")]
2331 {
2332 self.pending_updates = Default::default();
2333 }
2334
2335 self.pending_commit = Default::default();
2336
2337 Ok(())
2338 }
2339
2340 fn mls_rules(&self) -> Self::MlsRules {
2341 self.config.mls_rules()
2342 }
2343
2344 fn identity_provider(&self) -> Self::IdentityProvider {
2345 self.config.identity_provider()
2346 }
2347
2348 fn psk_storage(&self) -> Self::PreSharedKeyStorage {
2349 self.config.secret_store()
2350 }
2351
2352 fn group_state(&self) -> &GroupState {
2353 &self.state
2354 }
2355
2356 fn group_state_mut(&mut self) -> &mut GroupState {
2357 &mut self.state
2358 }
2359
2360 fn removal_proposal(
2361 &self,
2362 provisional_state: &ProvisionalState,
2363 ) -> Option<ProposalInfo<RemoveProposal>> {
2364 provisional_state
2365 .applied_proposals
2366 .removals
2367 .iter()
2368 .find(|p| p.proposal.to_remove == self.private_tree.self_index)
2369 .cloned()
2370 }
2371
2372 #[cfg(all(
2373 feature = "by_ref_proposal",
2374 feature = "custom_proposal",
2375 feature = "self_remove_proposal"
2376 ))]
2377 #[cfg_attr(feature = "ffi", safer_ffi_gen::safer_ffi_gen_ignore)]
2378 fn self_removal_proposal(
2379 &self,
2380 provisional_state: &ProvisionalState,
2381 ) -> Option<ProposalInfo<SelfRemoveProposal>> {
2382 provisional_state
2383 .applied_proposals
2384 .self_removes
2385 .iter()
2386 .find(|p| p.sender == Sender::Member(*self.private_tree.self_index))
2387 .cloned()
2388 }
2389
2390 #[cfg(feature = "private_message")]
2391 fn min_epoch_available(&self) -> Option<u64> {
2392 None
2393 }
2394
2395 fn cipher_suite_provider(&self) -> &Self::CipherSuiteProvider {
2396 &self.cipher_suite_provider
2397 }
2398}
2399
2400#[cfg(test)]
2401pub(crate) mod test_utils;
2402
2403#[cfg(test)]
2404mod tests {
2405 use crate::{
2406 client::test_utils::{
2407 test_client_with_key_pkg, TestClientBuilder, TEST_CIPHER_SUITE, TEST_PROTOCOL_VERSION,
2408 },
2409 client_builder::test_utils::TestClientConfig,
2410 crypto::test_utils::TestCryptoProvider,
2411 group::proposal_filter::ProposalInfo,
2412 identity::test_utils::get_test_signing_identity,
2413 key_package::test_utils::test_key_package_message,
2414 mls_rules::CommitOptions,
2415 tree_kem::{
2416 leaf_node::{test_utils::get_test_capabilities, LeafNodeSource},
2417 UpdatePathNode,
2418 },
2419 };
2420
2421 #[cfg(feature = "by_ref_proposal")]
2422 use crate::{
2423 client::test_utils::{test_client_with_key_pkg_custom, TEST_CUSTOM_PROPOSAL_TYPE},
2424 client_builder::{ClientBuilder, MlsConfig},
2425 group::{
2426 component_operation::ComponentID,
2427 mls_rules::{CommitDirection, CommitSource},
2428 proposal_filter::ProposalBundle,
2429 },
2430 identity::basic::BasicIdentityProvider,
2431 identity::test_utils::BasicWithCustomProvider,
2432 };
2433
2434 #[cfg(any(feature = "private_message", feature = "custom_proposal"))]
2435 use crate::group::mls_rules::DefaultMlsRules;
2436
2437 #[cfg(feature = "prior_epoch")]
2438 use crate::group::padding::PaddingMode;
2439
2440 use crate::{extension::RequiredCapabilitiesExt, key_package::test_utils::test_key_package};
2441
2442 #[cfg(all(feature = "by_ref_proposal", feature = "custom_proposal"))]
2443 use super::test_utils::test_group_custom_config;
2444
2445 #[cfg(any(feature = "psk", feature = "std"))]
2446 use crate::client::Client;
2447
2448 #[cfg(feature = "psk")]
2449 use crate::psk::PreSharedKey;
2450
2451 #[cfg(any(feature = "by_ref_proposal", feature = "private_message"))]
2452 use crate::group::test_utils::random_bytes;
2453
2454 #[cfg(feature = "by_ref_proposal")]
2455 use crate::{
2456 extension::test_utils::TestExtension, identity::test_utils::get_test_basic_credential,
2457 time::MlsTime,
2458 };
2459
2460 use super::{
2461 test_utils::{
2462 get_test_25519_key, get_test_groups_with_features, group_extensions, process_commit,
2463 test_group, test_group_custom, test_n_member_group, TestGroup, TEST_GROUP,
2464 },
2465 *,
2466 };
2467
2468 use assert_matches::assert_matches;
2469
2470 use message_processor::CommitEffect;
2471 use mls_rs_core::extension::{Extension, ExtensionType};
2472 use mls_rs_core::identity::{Credential, CredentialType, CustomCredential};
2473
2474 #[cfg(feature = "by_ref_proposal")]
2475 use mls_rs_core::identity::CertificateChain;
2476
2477 #[cfg(feature = "by_ref_proposal")]
2478 use crate::{crypto::test_utils::test_cipher_suite_provider, extension::ExternalSendersExt};
2479
2480 #[cfg(feature = "private_message")]
2481 use super::test_utils::test_member;
2482
2483 use mls_rs_core::extension::MlsExtension;
2484
2485 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2486 async fn test_create_group() {
2487 for (protocol_version, cipher_suite) in ProtocolVersion::all().flat_map(|p| {
2488 TestCryptoProvider::all_supported_cipher_suites()
2489 .into_iter()
2490 .map(move |cs| (p, cs))
2491 }) {
2492 let group = test_group(protocol_version, cipher_suite).await;
2493
2494 assert_eq!(group.cipher_suite(), cipher_suite);
2495 assert_eq!(group.state.context.epoch, 0);
2496 assert_eq!(group.state.context.group_id, TEST_GROUP.to_vec());
2497 assert_eq!(group.state.context.extensions, group_extensions());
2498
2499 assert_eq!(
2500 group.state.context.confirmed_transcript_hash,
2501 ConfirmedTranscriptHash::from(vec![])
2502 );
2503
2504 #[cfg(feature = "private_message")]
2505 assert!(group.state.proposals.is_empty());
2506
2507 #[cfg(feature = "by_ref_proposal")]
2508 assert!(group.pending_updates.is_empty());
2509
2510 assert!(!group.has_pending_commit());
2511
2512 assert_eq!(*group.private_tree.self_index, group.current_member_index());
2513 }
2514 }
2515
2516 #[cfg(feature = "private_message")]
2517 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2518 async fn test_pending_proposals_application_data() {
2519 let mut test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2520
2521 let (bob_key_package, _) =
2523 test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
2524
2525 let proposal = test_group
2526 .add_proposal(bob_key_package.key_package_message())
2527 .unwrap();
2528
2529 test_group.proposal_message(proposal, vec![]).await.unwrap();
2530
2531 let res = test_group
2533 .encrypt_application_message(b"test", vec![])
2534 .await;
2535
2536 assert_matches!(res, Err(MlsError::CommitRequired));
2537
2538 test_group.commit(vec![]).await.unwrap();
2540
2541 assert!(test_group.has_pending_commit());
2542
2543 test_group.apply_pending_commit().await.unwrap();
2544
2545 let res = test_group
2546 .encrypt_application_message(b"test", vec![])
2547 .await;
2548
2549 assert!(res.is_ok());
2550 }
2551
2552 #[cfg(feature = "by_ref_proposal")]
2553 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2554 async fn test_update_proposals() {
2555 let new_extension = TestExtension { foo: 10 };
2556 let mut extension_list = ExtensionList::default();
2557 extension_list.set_from(new_extension).unwrap();
2558
2559 let mut test_group = test_group_custom(
2560 TEST_PROTOCOL_VERSION,
2561 TEST_CIPHER_SUITE,
2562 vec![42.into()],
2563 Some(extension_list.clone()),
2564 None,
2565 )
2566 .await;
2567
2568 let existing_leaf = test_group.current_user_leaf_node().unwrap().clone();
2569
2570 let proposal = test_group.update_proposal().await;
2572
2573 let update = match proposal {
2574 Proposal::Update(update) => update,
2575 _ => panic!("non update proposal found"),
2576 };
2577
2578 assert_ne!(update.leaf_node.public_key, existing_leaf.public_key);
2579
2580 assert_eq!(
2581 update.leaf_node.signing_identity,
2582 existing_leaf.signing_identity
2583 );
2584
2585 assert_eq!(update.leaf_node.ungreased_extensions(), extension_list);
2586 assert_eq!(
2587 update.leaf_node.ungreased_capabilities().sorted(),
2588 Capabilities {
2589 extensions: vec![42.into()],
2590 ..get_test_capabilities()
2591 }
2592 .sorted()
2593 );
2594 }
2595
2596 #[cfg(feature = "by_ref_proposal")]
2597 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2598 async fn test_invalid_commit_self_update() {
2599 let mut test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2600
2601 let proposal_msg = test_group.propose_update(vec![]).await.unwrap();
2603
2604 let proposal = match proposal_msg.into_plaintext().unwrap().content.content {
2605 Content::Proposal(p) => p,
2606 _ => panic!("found non-proposal message"),
2607 };
2608
2609 let update_leaf = match *proposal {
2610 Proposal::Update(u) => u.leaf_node,
2611 _ => panic!("found proposal message that isn't an update"),
2612 };
2613
2614 test_group.commit(vec![]).await.unwrap();
2615 test_group.apply_pending_commit().await.unwrap();
2616
2617 assert_ne!(&update_leaf, test_group.current_user_leaf_node().unwrap());
2619 }
2620
2621 #[cfg(feature = "by_ref_proposal")]
2622 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2623 async fn update_proposal_with_bad_key_package_is_ignored_when_committing() {
2624 let (mut alice_group, mut bob_group) =
2625 test_two_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, true).await;
2626
2627 let mut proposal = alice_group.update_proposal().await;
2628
2629 if let Proposal::Update(ref mut update) = proposal {
2630 update.leaf_node.signature = random_bytes(32);
2631 } else {
2632 panic!("Invalid update proposal")
2633 }
2634
2635 let proposal_message = alice_group
2636 .proposal_message(proposal.clone(), vec![])
2637 .await
2638 .unwrap();
2639
2640 let proposal_plaintext = match proposal_message.payload {
2641 MlsMessagePayload::Plain(p) => p,
2642 _ => panic!("Unexpected non-plaintext message"),
2643 };
2644
2645 let proposal_ref = ProposalRef::from_content(
2646 &bob_group.cipher_suite_provider,
2647 &proposal_plaintext.clone().into(),
2648 )
2649 .await
2650 .unwrap();
2651
2652 bob_group
2654 .state
2655 .proposals
2656 .insert(proposal_ref, proposal, proposal_plaintext.content.sender);
2657
2658 let commit_output = bob_group.commit(vec![]).await.unwrap();
2659
2660 assert_matches!(
2661 commit_output.commit_message,
2662 MlsMessage {
2663 payload: MlsMessagePayload::Plain(
2664 PublicMessage {
2665 content: FramedContent {
2666 content: Content::Commit(c),
2667 ..
2668 },
2669 ..
2670 }),
2671 ..
2672 } if c.proposals.is_empty()
2673 );
2674 }
2675
2676 #[cfg(feature = "non_domain_separated_hpke_encrypt_decrypt")]
2677 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2678 async fn test_hpke_encrypt_decrypt() {
2679 let (alice_group, bob_group) =
2680 test_two_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, true).await;
2681 let receiver_index = alice_group.current_member_index();
2682 let sender_index = bob_group.current_member_index();
2683
2684 let context_info: Vec<u8> = vec![
2685 receiver_index.try_into().unwrap(),
2686 sender_index.try_into().unwrap(),
2687 ];
2688 let plaintext = b"message";
2689
2690 let hpke_ciphertext = bob_group
2691 .hpke_encrypt_to_recipient(receiver_index, &context_info, None, plaintext)
2692 .await
2693 .unwrap();
2694 let hpke_decrypted = alice_group
2695 .hpke_decrypt_for_current_member(&context_info, None, hpke_ciphertext)
2696 .await
2697 .unwrap();
2698
2699 assert_eq!(plaintext.to_vec(), hpke_decrypted);
2700 }
2701
2702 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2703 async fn safe_context_test_hpke_encrypt_decrypt() {
2704 let component_id: ComponentID = 1;
2705 let (alice_group, bob_group) =
2706 test_two_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, true).await;
2707 let receiver_index = alice_group.current_member_index();
2708 let sender_index = bob_group.current_member_index();
2709
2710 let context_info: Vec<u8> = vec![
2711 receiver_index.try_into().unwrap(),
2712 sender_index.try_into().unwrap(),
2713 ];
2714 let plaintext = b"message";
2715
2716 let hpke_ciphertext = bob_group
2717 .safe_encrypt_with_context_to_recipient(
2718 receiver_index,
2719 component_id,
2720 &context_info,
2721 None,
2722 plaintext,
2723 )
2724 .await
2725 .unwrap();
2726 let hpke_decrypted = alice_group
2727 .safe_decrypt_with_context_for_current_member(
2728 component_id,
2729 &context_info,
2730 None,
2731 hpke_ciphertext,
2732 )
2733 .await
2734 .unwrap();
2735
2736 assert_eq!(plaintext.to_vec(), hpke_decrypted);
2737 }
2738
2739 #[cfg(feature = "non_domain_separated_hpke_encrypt_decrypt")]
2740 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2741 async fn test_hpke_non_recipient_cant_decrypt() {
2742 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2743 let (mut bob, _) = alice.join("bob").await;
2744 let (carol, commit) = alice.join("carol").await;
2745
2746 bob.process_incoming_message(commit).await.unwrap();
2748
2749 let receiver_index = alice.current_member_index();
2750 let sender_index = bob.current_member_index();
2751
2752 let context_info: Vec<u8> = vec![
2753 receiver_index.try_into().unwrap(),
2754 sender_index.try_into().unwrap(),
2755 ];
2756 let plaintext = b"message";
2757
2758 let hpke_ciphertext = bob
2759 .hpke_encrypt_to_recipient(receiver_index, &context_info, None, plaintext)
2760 .await
2761 .unwrap();
2762
2763 let hpke_decrypted = carol
2765 .hpke_decrypt_for_current_member(&context_info, None, hpke_ciphertext)
2766 .await;
2767
2768 assert_matches!(hpke_decrypted, Err(MlsError::CryptoProviderError(_)));
2770 }
2771
2772 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2773 async fn safe_context_test_hpke_non_recipient_cant_decrypt() {
2774 let component_id: ComponentID = 345;
2775 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2776 let (mut bob, _) = alice.join("bob").await;
2777 let (carol, commit) = alice.join("carol").await;
2778
2779 bob.process_incoming_message(commit).await.unwrap();
2781
2782 let receiver_index = alice.current_member_index();
2783 let sender_index = bob.current_member_index();
2784
2785 let context_info: Vec<u8> = vec![
2786 receiver_index.try_into().unwrap(),
2787 sender_index.try_into().unwrap(),
2788 ];
2789 let plaintext = b"message";
2790
2791 let hpke_ciphertext = bob
2792 .safe_encrypt_with_context_to_recipient(
2793 receiver_index,
2794 component_id,
2795 &context_info,
2796 None,
2797 plaintext,
2798 )
2799 .await
2800 .unwrap();
2801
2802 let hpke_decrypted = carol
2804 .safe_decrypt_with_context_for_current_member(
2805 component_id,
2806 &context_info,
2807 None,
2808 hpke_ciphertext,
2809 )
2810 .await;
2811
2812 assert_matches!(hpke_decrypted, Err(MlsError::CryptoProviderError(_)));
2814 }
2815
2816 #[cfg(feature = "non_domain_separated_hpke_encrypt_decrypt")]
2817 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2818 async fn test_hpke_can_decrypt_after_group_changes() {
2819 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2820 let (mut bob, _) = alice.join("bob").await;
2821
2822 let receiver_index = alice.current_member_index();
2823 let sender_index = bob.current_member_index();
2824 let context_info: Vec<u8> = vec![
2825 receiver_index.try_into().unwrap(),
2826 sender_index.try_into().unwrap(),
2827 ];
2828 let associated_data: Vec<u8> = vec![1, 2, 3, 4];
2829 let plaintext = b"message";
2830
2831 let hpke_ciphertext = bob
2833 .hpke_encrypt_to_recipient(
2834 receiver_index,
2835 &context_info,
2836 Some(&associated_data),
2837 plaintext,
2838 )
2839 .await
2840 .unwrap();
2841
2842 let (_carol, commit) = alice.join("carol").await;
2844 bob.process_incoming_message(commit).await.unwrap();
2845
2846 let hpke_decrypted = alice
2848 .hpke_decrypt_for_current_member(&context_info, Some(&associated_data), hpke_ciphertext)
2849 .await
2850 .unwrap();
2851 assert_eq!(plaintext.to_vec(), hpke_decrypted);
2852 }
2853
2854 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2855 async fn safe_context_test_hpke_can_decrypt_after_group_changes() {
2856 let component_id: ComponentID = 2;
2857 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2858 let (mut bob, _) = alice.join("bob").await;
2859
2860 let receiver_index = alice.current_member_index();
2861 let sender_index = bob.current_member_index();
2862 let context_info: Vec<u8> = vec![
2863 receiver_index.try_into().unwrap(),
2864 sender_index.try_into().unwrap(),
2865 ];
2866 let associated_data: Vec<u8> = vec![1, 2, 3, 4];
2867 let plaintext = b"message";
2868
2869 let hpke_ciphertext = bob
2871 .safe_encrypt_with_context_to_recipient(
2872 receiver_index,
2873 component_id,
2874 &context_info,
2875 Some(&associated_data),
2876 plaintext,
2877 )
2878 .await
2879 .unwrap();
2880
2881 let (_carol, commit) = alice.join("carol").await;
2883 bob.process_incoming_message(commit).await.unwrap();
2884
2885 let hpke_decrypted = alice
2887 .safe_decrypt_with_context_for_current_member(
2888 component_id,
2889 &context_info,
2890 Some(&associated_data),
2891 hpke_ciphertext,
2892 )
2893 .await
2894 .unwrap();
2895 assert_eq!(plaintext.to_vec(), hpke_decrypted);
2896 }
2897
2898 #[cfg(all(
2899 feature = "prior_epoch",
2900 feature = "custom_proposal",
2901 feature = "by_ref_proposal",
2902 feature = "prior_epoch_membership_key"
2903 ))]
2904 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2905 async fn test_can_validate_and_get_data_custom_proposal_from_past_epoch() {
2906 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2907 let (mut bob, _) = alice.join("bob").await;
2908
2909 let data = vec![1, 2, 3];
2910 let custom_proposal = CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![4, 5, 6]);
2911 let proposal = alice
2912 .propose_custom(custom_proposal.clone(), data.clone())
2913 .await
2914 .unwrap();
2915
2916 let (_carol, commit) = alice.join("carol").await;
2918 bob.process_incoming_message(commit).await.unwrap();
2919 let (validated_data, sender) = bob
2920 .validate_custom_proposal(&proposal, Some(TEST_CUSTOM_PROPOSAL_TYPE))
2921 .await
2922 .unwrap();
2923 assert_eq!(data, validated_data);
2924 assert_eq!(sender, Sender::Member(0));
2925 }
2926
2927 #[cfg(all(
2928 feature = "prior_epoch",
2929 feature = "custom_proposal",
2930 feature = "by_ref_proposal",
2931 feature = "prior_epoch_membership_key"
2932 ))]
2933 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2934 async fn test_can_validate_and_get_data_custom_proposal_from_current_epoch() {
2935 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2936 let (mut bob, _) = alice.join("bob").await;
2937
2938 let data = vec![1, 2, 3];
2939 let custom_proposal = CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![3, 4, 5]);
2940 let proposal = alice
2941 .propose_custom(custom_proposal.clone(), data.clone())
2942 .await
2943 .unwrap();
2944
2945 let (validated_data, sender) = bob.validate_custom_proposal(&proposal, None).await.unwrap();
2946 assert_eq!(data, validated_data);
2947 assert_eq!(sender, Sender::Member(0));
2948 }
2949
2950 #[cfg(all(feature = "prior_epoch", feature = "prior_epoch_membership_key"))]
2951 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2952 async fn test_can_validate_non_custom_proposal_from_past_epoch() {
2953 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2954 let (mut bob, _) = alice.join("bob").await;
2955
2956 let proposal = alice.propose_update(Vec::new()).await.unwrap();
2957
2958 let (_carol, commit) = alice.join("carol").await;
2960 bob.process_incoming_message(commit).await.unwrap();
2961
2962 bob.validate_public_message(&proposal).await.unwrap();
2963 }
2964
2965 #[cfg(all(
2966 feature = "prior_epoch",
2967 feature = "by_ref_proposal",
2968 feature = "prior_epoch_membership_key"
2969 ))]
2970 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2971 async fn test_cannot_validate_custom_proposal_for_non_custom_proposal() {
2972 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2973 let (mut bob, _) = alice.join("bob").await;
2974
2975 let proposal = alice.propose_update(Vec::new()).await.unwrap();
2976
2977 let (_carol, commit) = alice.join("carol").await;
2979 bob.process_incoming_message(commit).await.unwrap();
2980
2981 let data_err = bob.validate_custom_proposal(&proposal, None).await;
2982 assert_matches!(data_err, Err(MlsError::UnexpectedMessageType));
2983 }
2984
2985 #[cfg(all(feature = "prior_epoch", feature = "prior_epoch_membership_key"))]
2986 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
2987 async fn test_can_validate_commit_from_past_epoch() {
2988 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
2989 let (mut bob, _) = alice.join("bob").await;
2990
2991 let old_commit_output = bob
2992 .commit_builder()
2993 .remove_member(0)
2994 .unwrap()
2995 .build()
2996 .await
2997 .unwrap();
2998
2999 let (_carol, commit) = alice.join("carol").await;
3001 bob.process_incoming_message(commit).await.unwrap();
3002
3003 bob.validate_public_message(&old_commit_output.commit_message)
3004 .await
3005 .unwrap();
3006 }
3007
3008 #[cfg(all(
3009 feature = "prior_epoch",
3010 feature = "custom_proposal",
3011 feature = "prior_epoch_membership_key"
3012 ))]
3013 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3014 async fn test_can_validate_custom_proposal_from_current_epoch() {
3015 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3016 let (mut bob, _) = alice.join("bob").await;
3017
3018 let custom_proposal = CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![0, 1, 2]);
3019 let proposal = alice
3020 .propose_custom(custom_proposal.clone(), vec![])
3021 .await
3022 .unwrap();
3023
3024 bob.validate_public_message(&proposal).await.unwrap();
3025 }
3026
3027 #[cfg(all(feature = "prior_epoch", feature = "prior_epoch_membership_key"))]
3028 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3029 async fn test_cannot_validate_non_public_message() {
3030 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3031 let (mut bob, _) = alice.join("bob").await;
3032
3033 let res = alice
3034 .encrypt_application_message(b"test", vec![])
3035 .await
3036 .unwrap();
3037
3038 let auth_content = bob.validate_public_message(&res).await;
3039 assert_matches!(auth_content, Err(MlsError::UnexpectedMessageType));
3040 }
3041
3042 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
3043 async fn test_two_member_group(
3044 protocol_version: ProtocolVersion,
3045 cipher_suite: CipherSuite,
3046 tree_ext: bool,
3047 ) -> (TestGroup, TestGroup) {
3048 let mut test_group = test_group_custom(
3049 protocol_version,
3050 cipher_suite,
3051 Default::default(),
3052 None,
3053 Some(CommitOptions::new().with_ratchet_tree_extension(tree_ext)),
3054 )
3055 .await;
3056
3057 let (bob_test_group, _) = test_group.join("bob").await;
3058
3059 assert!(Group::equal_group_state(&test_group, &bob_test_group));
3060
3061 (test_group, bob_test_group)
3062 }
3063
3064 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3065 async fn test_welcome_processing_exported_tree() {
3066 test_two_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, false).await;
3067 }
3068
3069 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3070 async fn test_welcome_processing_tree_extension() {
3071 test_two_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, true).await;
3072 }
3073
3074 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3075 async fn test_welcome_processing_missing_tree() {
3076 let mut test_group = test_group_custom(
3077 TEST_PROTOCOL_VERSION,
3078 TEST_CIPHER_SUITE,
3079 Default::default(),
3080 None,
3081 Some(CommitOptions::new().with_ratchet_tree_extension(false)),
3082 )
3083 .await;
3084
3085 let (bob_client, bob_key_package) =
3086 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
3087
3088 let commit_output = test_group
3090 .commit_builder()
3091 .add_member(bob_key_package)
3092 .unwrap()
3093 .build()
3094 .await
3095 .unwrap();
3096
3097 let bob_group = Group::join(
3099 &commit_output.welcome_messages[0],
3100 None,
3101 bob_client.config,
3102 bob_client.signer.unwrap(),
3103 None,
3104 )
3105 .await
3106 .map(|_| ());
3107
3108 assert_matches!(bob_group, Err(MlsError::RatchetTreeNotFound));
3109 }
3110
3111 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3112 async fn test_reused_key_package() {
3113 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3114 let (bob_client, bob_key_package) =
3115 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
3116 let mut carla_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3117
3118 let commit_output = alice_group
3120 .group
3121 .commit_builder()
3122 .add_member(bob_key_package.clone())
3123 .unwrap()
3124 .build()
3125 .await
3126 .unwrap();
3127
3128 let (mut bob_group, _) = bob_client
3130 .join_group(None, &commit_output.welcome_messages[0], None)
3131 .await
3132 .unwrap();
3133 bob_group.write_to_storage().await.unwrap();
3135
3136 let commit_output = carla_group
3138 .group
3139 .commit_builder()
3140 .add_member(bob_key_package.clone())
3141 .unwrap()
3142 .build()
3143 .await
3144 .unwrap();
3145
3146 let bob_group = bob_client
3148 .join_group(None, &commit_output.welcome_messages[0], None)
3149 .await
3150 .map(|_| ());
3151 assert_matches!(bob_group, Err(MlsError::WelcomeKeyPackageNotFound));
3152 }
3153
3154 #[cfg(feature = "last_resort_key_package_ext")]
3155 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3156 async fn test_last_resort_key_package() -> Result<(), MlsError> {
3157 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3158 let (bob_client, bob_key_package) = test_client_with_key_pkg_custom(
3159 TEST_PROTOCOL_VERSION,
3160 TEST_CIPHER_SUITE,
3161 "bob",
3162 vec![LastResortKeyPackageExt.into_extension().unwrap()].into(),
3163 Default::default(),
3164 |_| {},
3165 )
3166 .await;
3167 let mut carla_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3168
3169 let commit_output = alice_group
3171 .group
3172 .commit_builder()
3173 .add_member(bob_key_package.clone())?
3174 .build()
3175 .await?;
3176
3177 let (mut bob_group, _) = bob_client
3179 .join_group(None, &commit_output.welcome_messages[0], None)
3180 .await?;
3181 bob_group.write_to_storage()?;
3183
3184 let commit_output = carla_group
3186 .group
3187 .commit_builder()
3188 .add_member(bob_key_package.clone())?
3189 .build()
3190 .await?;
3191
3192 bob_client
3194 .join_group(None, &commit_output.welcome_messages[0], None)
3195 .await?;
3196
3197 Ok(())
3198 }
3199
3200 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3201 async fn test_group_context_ext_proposal_create() {
3202 let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3203
3204 let mut extension_list = ExtensionList::new();
3205 extension_list
3206 .set_from(RequiredCapabilitiesExt {
3207 extensions: vec![42.into()],
3208 proposals: vec![],
3209 credentials: vec![],
3210 })
3211 .unwrap();
3212
3213 let proposal = test_group.group_context_extensions_proposal(extension_list.clone());
3214
3215 assert_matches!(proposal, Proposal::GroupContextExtensions(ext) if ext == extension_list);
3216 }
3217
3218 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
3219 async fn group_context_extension_proposal_test(
3220 ext_list: ExtensionList,
3221 ) -> (TestGroup, Result<MlsMessage, MlsError>) {
3222 let protocol_version = TEST_PROTOCOL_VERSION;
3223 let cipher_suite = TEST_CIPHER_SUITE;
3224
3225 let mut test_group =
3226 test_group_custom(protocol_version, cipher_suite, vec![42.into()], None, None).await;
3227
3228 let commit = test_group
3229 .commit_builder()
3230 .set_group_context_ext(ext_list)
3231 .unwrap()
3232 .build()
3233 .await
3234 .map(|commit_output| commit_output.commit_message);
3235
3236 (test_group, commit)
3237 }
3238
3239 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3240 async fn test_group_context_ext_proposal_commit() {
3241 use message_processor::CommitEffect;
3242
3243 let mut extension_list = ExtensionList::new();
3244
3245 extension_list
3246 .set_from(RequiredCapabilitiesExt {
3247 extensions: vec![42.into()],
3248 proposals: vec![],
3249 credentials: vec![],
3250 })
3251 .unwrap();
3252
3253 let (mut test_group, _) =
3254 group_context_extension_proposal_test(extension_list.clone()).await;
3255
3256 let update = test_group.apply_pending_commit().await.unwrap();
3257
3258 assert_matches!(update.effect, CommitEffect::NewEpoch(_));
3260
3261 assert_eq!(test_group.state.context.extensions, extension_list)
3262 }
3263
3264 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3265 async fn test_group_context_ext_proposal_invalid() {
3266 let mut extension_list = ExtensionList::new();
3267 extension_list
3268 .set_from(RequiredCapabilitiesExt {
3269 extensions: vec![999.into()],
3270 proposals: vec![],
3271 credentials: vec![],
3272 })
3273 .unwrap();
3274
3275 let (_, commit) = group_context_extension_proposal_test(extension_list.clone()).await;
3276
3277 assert_matches!(
3278 commit,
3279 Err(MlsError::RequiredExtensionNotFound(a)) if a == 999.into()
3280 );
3281 }
3282
3283 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
3284 async fn make_group_with_required_capabilities(
3285 required_caps: RequiredCapabilitiesExt,
3286 ) -> Result<Group<TestClientConfig>, MlsError> {
3287 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "alice")
3288 .await
3289 .0
3290 .create_group(
3291 core::iter::once(required_caps.into_extension().unwrap()).collect(),
3292 Default::default(),
3293 None,
3294 )
3295 .await
3296 }
3297
3298 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3299 async fn creating_group_with_member_not_supporting_required_credential_type_fails() {
3300 let group_creation = make_group_with_required_capabilities(RequiredCapabilitiesExt {
3301 credentials: vec![CredentialType::BASIC, CredentialType::X509],
3302 ..Default::default()
3303 })
3304 .await
3305 .map(|_| ());
3306
3307 assert_matches!(
3308 group_creation,
3309 Err(MlsError::RequiredCredentialNotFound(CredentialType::X509))
3310 );
3311 }
3312
3313 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3314 async fn creating_group_with_member_not_supporting_required_extension_type_fails() {
3315 const EXTENSION_TYPE: ExtensionType = ExtensionType::new(33);
3316
3317 let group_creation = make_group_with_required_capabilities(RequiredCapabilitiesExt {
3318 extensions: vec![EXTENSION_TYPE],
3319 ..Default::default()
3320 })
3321 .await
3322 .map(|_| ());
3323
3324 assert_matches!(
3325 group_creation,
3326 Err(MlsError::RequiredExtensionNotFound(EXTENSION_TYPE))
3327 );
3328 }
3329
3330 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3331 async fn creating_group_with_member_not_supporting_required_proposal_type_fails() {
3332 const PROPOSAL_TYPE: ProposalType = ProposalType::new(33);
3333
3334 let group_creation = make_group_with_required_capabilities(RequiredCapabilitiesExt {
3335 proposals: vec![PROPOSAL_TYPE],
3336 ..Default::default()
3337 })
3338 .await
3339 .map(|_| ());
3340
3341 assert_matches!(
3342 group_creation,
3343 Err(MlsError::RequiredProposalNotFound(PROPOSAL_TYPE))
3344 );
3345 }
3346
3347 #[cfg(feature = "by_ref_proposal")]
3348 #[cfg(not(target_arch = "wasm32"))]
3349 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3350 async fn creating_group_with_member_not_supporting_external_sender_credential_fails() {
3351 let ext_senders = make_x509_external_senders_ext()
3352 .await
3353 .into_extension()
3354 .unwrap();
3355
3356 let group_creation =
3357 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "alice")
3358 .await
3359 .0
3360 .create_group(
3361 core::iter::once(ext_senders).collect(),
3362 Default::default(),
3363 None,
3364 )
3365 .await
3366 .map(|_| ());
3367
3368 assert_matches!(
3369 group_creation,
3370 Err(MlsError::RequiredCredentialNotFound(CredentialType::X509))
3371 );
3372 }
3373
3374 #[cfg(all(not(target_arch = "wasm32"), feature = "private_message"))]
3375 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3376 async fn test_group_encrypt_plaintext_padding() {
3377 let protocol_version = TEST_PROTOCOL_VERSION;
3378 let cipher_suite = CipherSuite::CURVE25519_AES128;
3380
3381 let mut test_group = test_group_custom_config(protocol_version, cipher_suite, |b| {
3382 b.mls_rules(
3383 DefaultMlsRules::default()
3384 .with_encryption_options(EncryptionOptions::new(true, PaddingMode::None)),
3385 )
3386 })
3387 .await;
3388
3389 let without_padding = test_group
3390 .encrypt_application_message(&random_bytes(150), vec![])
3391 .await
3392 .unwrap();
3393
3394 let mut test_group =
3395 test_group_custom_config(protocol_version, cipher_suite, |b| {
3396 b.mls_rules(DefaultMlsRules::default().with_encryption_options(
3397 EncryptionOptions::new(true, PaddingMode::StepFunction),
3398 ))
3399 })
3400 .await;
3401
3402 let with_step_function_padding = test_group
3403 .encrypt_application_message(&random_bytes(150), vec![])
3404 .await
3405 .unwrap();
3406
3407 assert!(with_step_function_padding.mls_encoded_len() > without_padding.mls_encoded_len());
3408
3409 let mut test_group = test_group_custom_config(protocol_version, cipher_suite, |b| {
3410 b.mls_rules(
3411 DefaultMlsRules::default()
3412 .with_encryption_options(EncryptionOptions::new(true, PaddingMode::Padme)),
3413 )
3414 })
3415 .await;
3416
3417 let with_padme_padding = test_group
3418 .encrypt_application_message(&random_bytes(150), vec![])
3419 .await
3420 .unwrap();
3421
3422 assert!(with_padme_padding.mls_encoded_len() > without_padding.mls_encoded_len());
3423 }
3424
3425 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3426 async fn external_commit_requires_external_pub_extension() {
3427 let protocol_version = TEST_PROTOCOL_VERSION;
3428 let cipher_suite = TEST_CIPHER_SUITE;
3429 let group = test_group(protocol_version, cipher_suite).await;
3430
3431 let info = group
3432 .group_info_message(false)
3433 .await
3434 .unwrap()
3435 .into_group_info()
3436 .unwrap();
3437
3438 let info_msg = MlsMessage::new(protocol_version, MlsMessagePayload::GroupInfo(info));
3439
3440 let signing_identity = group.current_member_signing_identity().unwrap().clone();
3441
3442 let res = external_commit::ExternalCommitBuilder::new(
3443 group.group.signer,
3444 signing_identity,
3445 group.group.config,
3446 )
3447 .build(info_msg)
3448 .await
3449 .map(|_| {});
3450
3451 assert_matches!(res, Err(MlsError::MissingExternalPubExtension));
3452 }
3453
3454 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3455 async fn external_commit_via_commit_options_round_trip() {
3456 let mut group = test_group_custom(
3457 TEST_PROTOCOL_VERSION,
3458 TEST_CIPHER_SUITE,
3459 vec![],
3460 None,
3461 CommitOptions::default()
3462 .with_allow_external_commit(true)
3463 .into(),
3464 )
3465 .await;
3466
3467 let commit_output = group.commit(vec![]).await.unwrap();
3468
3469 let (test_client, _) =
3470 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
3471
3472 test_client
3473 .external_commit_builder()
3474 .unwrap()
3475 .build(commit_output.external_commit_group_info.unwrap())
3476 .await
3477 .unwrap();
3478 }
3479
3480 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3481 async fn test_path_update_preference() {
3482 let protocol_version = TEST_PROTOCOL_VERSION;
3483 let cipher_suite = TEST_CIPHER_SUITE;
3484
3485 let mut test_group = test_group_custom(
3486 protocol_version,
3487 cipher_suite,
3488 Default::default(),
3489 None,
3490 Some(CommitOptions::new()),
3491 )
3492 .await;
3493
3494 let test_key_package =
3495 test_key_package_message(protocol_version, cipher_suite, "alice").await;
3496
3497 let has_path = test_group
3498 .commit_builder()
3499 .add_member(test_key_package.clone())
3500 .unwrap()
3501 .build()
3502 .await
3503 .unwrap()
3504 .contains_update_path;
3505
3506 assert!(!has_path);
3507
3508 let mut test_group = test_group_custom(
3509 protocol_version,
3510 cipher_suite,
3511 Default::default(),
3512 None,
3513 Some(CommitOptions::new().with_path_required(true)),
3514 )
3515 .await;
3516
3517 let has_path = test_group
3518 .commit_builder()
3519 .add_member(test_key_package)
3520 .unwrap()
3521 .build()
3522 .await
3523 .unwrap()
3524 .contains_update_path;
3525
3526 assert!(has_path)
3527 }
3528
3529 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3530 async fn test_path_update_preference_override() {
3531 let protocol_version = TEST_PROTOCOL_VERSION;
3532 let cipher_suite = TEST_CIPHER_SUITE;
3533
3534 let mut test_group = test_group_custom(
3535 protocol_version,
3536 cipher_suite,
3537 Default::default(),
3538 None,
3539 Some(CommitOptions::new()),
3540 )
3541 .await;
3542
3543 let has_path = test_group
3544 .commit(vec![])
3545 .await
3546 .unwrap()
3547 .contains_update_path;
3548
3549 assert!(has_path);
3550 }
3551
3552 #[cfg(feature = "private_message")]
3553 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3554 async fn group_rejects_unencrypted_application_message() {
3555 let protocol_version = TEST_PROTOCOL_VERSION;
3556 let cipher_suite = TEST_CIPHER_SUITE;
3557
3558 let mut alice = test_group(protocol_version, cipher_suite).await;
3559 let (mut bob, _) = alice.join("bob").await;
3560
3561 let message = alice
3562 .make_plaintext(Content::Application(b"hello".to_vec().into()))
3563 .await;
3564
3565 let res = bob.process_incoming_message(message).await;
3566
3567 assert_matches!(res, Err(MlsError::UnencryptedApplicationMessage));
3568 }
3569
3570 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3571 async fn commit_description_external_commit() {
3572 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3573
3574 let (bob_identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;
3575
3576 let bob = TestClientBuilder::new_for_test()
3577 .signing_identity(bob_identity, secret_key, TEST_CIPHER_SUITE)
3578 .build();
3579
3580 let (bob_group, commit) = bob
3581 .external_commit_builder()
3582 .unwrap()
3583 .build(
3584 alice_group
3585 .group_info_message_allowing_ext_commit(true)
3586 .await
3587 .unwrap(),
3588 )
3589 .await
3590 .unwrap();
3591
3592 let event = alice_group.process_message(commit).await.unwrap();
3593
3594 let ReceivedMessage::Commit(commit_description) = event else {
3595 panic!("expected commit");
3596 };
3597
3598 assert!(commit_description.is_external);
3599 assert_eq!(commit_description.committer, 1);
3600
3601 let new_epoch = match commit_description.effect {
3602 CommitEffect::NewEpoch(new_epoch) => new_epoch,
3603 _ => panic!("unexpected commit effect"),
3604 };
3605
3606 assert_eq!(new_epoch.applied_proposals.len(), 1);
3607
3608 itertools::assert_equal(
3609 bob_group.roster().members_iter(),
3610 alice_group.roster().members_iter(),
3611 );
3612 }
3613
3614 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3615 async fn can_join_new_group_externally() {
3616 use crate::client::test_utils::TestClientBuilder;
3617
3618 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3619
3620 let (bob_identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;
3621
3622 let bob = TestClientBuilder::new_for_test()
3623 .signing_identity(bob_identity, secret_key, TEST_CIPHER_SUITE)
3624 .build();
3625
3626 let (_, commit) = bob
3627 .external_commit_builder()
3628 .unwrap()
3629 .with_tree_data(alice_group.export_tree().into_owned())
3630 .build(
3631 alice_group
3632 .group_info_message_allowing_ext_commit(false)
3633 .await
3634 .unwrap(),
3635 )
3636 .await
3637 .unwrap();
3638
3639 alice_group.process_message(commit).await.unwrap();
3640 }
3641
3642 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3643 async fn test_membership_tag_from_non_member() {
3644 let (mut alice_group, mut bob_group) =
3645 test_two_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, true).await;
3646
3647 let mut commit_output = alice_group.commit(vec![]).await.unwrap();
3648
3649 let plaintext = match commit_output.commit_message.payload {
3650 MlsMessagePayload::Plain(ref mut plain) => plain,
3651 _ => panic!("Non plaintext message"),
3652 };
3653
3654 plaintext.content.sender = Sender::NewMemberCommit;
3655
3656 let res = bob_group
3657 .process_message(commit_output.commit_message)
3658 .await;
3659
3660 assert_matches!(res, Err(MlsError::MembershipTagForNonMember));
3661 }
3662
3663 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3664 async fn test_partial_commits() {
3665 let protocol_version = TEST_PROTOCOL_VERSION;
3666 let cipher_suite = TEST_CIPHER_SUITE;
3667
3668 let mut alice = test_group(protocol_version, cipher_suite).await;
3669 let (mut bob, _) = alice.join("bob").await;
3670 let (mut charlie, commit) = alice.join("charlie").await;
3671 bob.process_message(commit).await.unwrap();
3672
3673 let (_, commit) = charlie.join("dave").await;
3674
3675 alice.process_message(commit.clone()).await.unwrap();
3676 bob.process_message(commit.clone()).await.unwrap();
3677
3678 let Content::Commit(commit) = commit.into_plaintext().unwrap().content.content else {
3679 panic!("Expected commit")
3680 };
3681
3682 assert!(commit.path.is_none());
3683 }
3684
3685 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
3686 async fn group_with_path_required() -> TestGroup {
3687 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3688
3689 alice.config.0.mls_rules.commit_options.path_required = true;
3690
3691 alice
3692 }
3693
3694 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3695 async fn old_hpke_secrets_are_removed() {
3696 let mut alice = group_with_path_required().await;
3697 alice.join("bob").await;
3698 alice.join("charlie").await;
3699
3700 alice
3701 .commit_builder()
3702 .remove_member(1)
3703 .unwrap()
3704 .build()
3705 .await
3706 .unwrap();
3707
3708 assert!(alice.private_tree.secret_keys[1].is_some());
3709 alice.process_pending_commit().await.unwrap();
3710 assert!(alice.private_tree.secret_keys[1].is_none());
3711 }
3712
3713 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3714 async fn old_hpke_secrets_of_removed_are_removed() {
3715 let mut alice = group_with_path_required().await;
3716 alice.join("bob").await;
3717 let (mut charlie, _) = alice.join("charlie").await;
3718
3719 let commit = charlie
3720 .commit_builder()
3721 .remove_member(1)
3722 .unwrap()
3723 .build()
3724 .await
3725 .unwrap();
3726
3727 assert!(alice.private_tree.secret_keys[1].is_some());
3728 alice.process_message(commit.commit_message).await.unwrap();
3729 assert!(alice.private_tree.secret_keys[1].is_none());
3730 }
3731
3732 #[cfg(feature = "by_ref_proposal")]
3733 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3734 async fn old_hpke_secrets_of_updated_are_removed() {
3735 let mut alice = group_with_path_required().await;
3736 let (mut bob, _) = alice.join("bob").await;
3737 let (mut charlie, commit) = alice.join("charlie").await;
3738 bob.process_message(commit).await.unwrap();
3739
3740 let update = bob.propose_update(vec![]).await.unwrap();
3741 charlie.process_message(update.clone()).await.unwrap();
3742 alice.process_message(update).await.unwrap();
3743
3744 let commit = charlie.commit(vec![]).await.unwrap();
3745
3746 assert!(alice.private_tree.secret_keys[1].is_some());
3747 alice.process_message(commit.commit_message).await.unwrap();
3748 assert!(alice.private_tree.secret_keys[1].is_none());
3749 }
3750
3751 #[cfg(feature = "psk")]
3752 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3753 async fn only_selected_members_of_the_original_group_can_join_subgroup() {
3754 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3755 let (mut bob, _) = alice.join("bob").await;
3756 let (carol, commit) = alice.join("carol").await;
3757
3758 bob.process_incoming_message(commit).await.unwrap();
3760
3761 let bob_identity = bob.current_member_signing_identity().unwrap().clone();
3762 let signer = bob.signer.clone();
3763
3764 let new_key_pkg = Client::new(
3765 bob.config.clone(),
3766 Some(signer),
3767 Some((bob_identity, TEST_CIPHER_SUITE)),
3768 TEST_PROTOCOL_VERSION,
3769 )
3770 .generate_key_package_message(Default::default(), Default::default(), None)
3771 .await
3772 .unwrap();
3773
3774 let (mut alice_sub_group, welcome) = alice
3775 .branch(b"subgroup".to_vec(), vec![new_key_pkg], None)
3776 .await
3777 .unwrap();
3778
3779 let welcome = &welcome[0];
3780
3781 let (mut bob_sub_group, _) = bob.join_subgroup(welcome, None, None).await.unwrap();
3782
3783 let res = carol.join_subgroup(welcome, None, None).await.map(|_| ());
3785 assert_matches!(res, Err(_));
3786
3787 let commit_output = alice_sub_group.commit(vec![]).await.unwrap();
3789
3790 bob_sub_group
3791 .process_incoming_message(commit_output.commit_message)
3792 .await
3793 .unwrap();
3794 }
3795
3796 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
3797 async fn joining_group_fails_if_unsupported<F>(
3798 f: F,
3799 ) -> Result<(TestGroup, MlsMessage), MlsError>
3800 where
3801 F: FnMut(&mut TestClientConfig),
3802 {
3803 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3804 alice_group.join_with_custom_config("alice", false, f).await
3805 }
3806
3807 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3808 async fn joining_group_fails_if_protocol_version_is_not_supported() {
3809 let res = joining_group_fails_if_unsupported(|config| {
3810 config.0.settings.protocol_versions.clear();
3811 })
3812 .await
3813 .map(|_| ());
3814
3815 assert_matches!(
3816 res,
3817 Err(MlsError::UnsupportedProtocolVersion(v)) if v ==
3818 TEST_PROTOCOL_VERSION
3819 );
3820 }
3821
3822 #[cfg(not(target_arch = "wasm32"))]
3824 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3825 async fn joining_group_fails_if_cipher_suite_is_not_supported() {
3826 let res = joining_group_fails_if_unsupported(|config| {
3827 config
3828 .0
3829 .crypto_provider
3830 .enabled_cipher_suites
3831 .retain(|&x| x != TEST_CIPHER_SUITE);
3832 })
3833 .await
3834 .map(|_| ());
3835
3836 assert_matches!(
3837 res,
3838 Err(MlsError::UnsupportedCipherSuite(TEST_CIPHER_SUITE))
3839 );
3840 }
3841
3842 #[cfg(feature = "private_message")]
3843 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3844 async fn member_can_see_sender_creds() {
3845 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3846 let (mut bob_group, _) = alice_group.join("bob").await;
3847
3848 let bob_msg = b"I'm Bob";
3849
3850 let msg = bob_group
3851 .encrypt_application_message(bob_msg, vec![])
3852 .await
3853 .unwrap();
3854
3855 let received_by_alice = alice_group.process_incoming_message(msg).await.unwrap();
3856
3857 assert_matches!(
3858 received_by_alice,
3859 ReceivedMessage::ApplicationMessage(ApplicationMessageDescription { sender_index, .. })
3860 if sender_index == bob_group.current_member_index()
3861 );
3862 }
3863
3864 #[cfg(all(
3865 feature = "export_key_generation",
3866 feature = "private_message",
3867 feature = "secret_tree_access",
3868 ))]
3869 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3870 async fn export_and_verify_key_generation() {
3871 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3872 let (mut bob_group, _) = alice_group.join("bob").await;
3873
3874 for i in 0..10 {
3875 let key_gen = bob_group.peek_next_key_generation().unwrap();
3876 assert_eq!(key_gen, i);
3877
3878 let authn_key_gen = key_gen.to_be_bytes();
3879 let msg = bob_group
3880 .encrypt_application_message(&authn_key_gen, vec![])
3881 .await
3882 .unwrap();
3883
3884 let received_by_alice = alice_group.process_incoming_message(msg).await.unwrap();
3885 assert_matches!(
3886 received_by_alice,
3887 ReceivedMessage::ApplicationMessage(ApplicationMessageDescription { unauthenticated_key_generation, .. })
3888 if unauthenticated_key_generation.unwrap().to_be_bytes() == authn_key_gen
3889 );
3890 }
3891 }
3892
3893 #[cfg(all(feature = "export_key_generation", feature = "private_message",))]
3894 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3895 async fn verify_key_generation() {
3896 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3897 let (mut bob_group, _) = alice_group.join("bob").await;
3898
3899 for i in 0u32..10u32 {
3900 let bob_msg = i.to_be_bytes();
3901 let msg = bob_group
3902 .encrypt_application_message(&bob_msg, vec![])
3903 .await
3904 .unwrap();
3905
3906 let received_by_alice = alice_group.process_incoming_message(msg).await.unwrap();
3907 assert_matches!(
3908 received_by_alice,
3909 ReceivedMessage::ApplicationMessage(ApplicationMessageDescription { unauthenticated_key_generation, .. })
3910 if unauthenticated_key_generation.unwrap().to_be_bytes() == bob_msg
3911 );
3912 }
3913 }
3914
3915 #[cfg(all(
3916 feature = "export_key_generation",
3917 feature = "private_message",
3918 feature = "prior_epoch",
3919 feature = "secret_tree_access"
3920 ))]
3921 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3922 async fn verify_key_generation_from_prior_epoch() {
3923 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3924 let (mut bob_group, _) = alice_group.join("bob").await;
3925
3926 let key_gen = bob_group.peek_next_key_generation().unwrap();
3927
3928 let alice_key_gen = alice_group.peek_next_key_generation().unwrap();
3929 assert!(alice_key_gen == key_gen);
3930
3931 let authn_key_gen = key_gen.to_be_bytes();
3932 let msg = bob_group
3933 .encrypt_application_message(&authn_key_gen, vec![])
3934 .await
3935 .unwrap();
3936
3937 alice_group
3938 .encrypt_application_message(&[1, 2, 3], vec![])
3939 .await
3940 .unwrap();
3941
3942 let alice_key_gen = alice_group.peek_next_key_generation().unwrap();
3945 assert!(alice_key_gen != key_gen);
3946
3947 alice_group.commit(vec![]).await.unwrap();
3950 assert!(alice_group.has_pending_commit());
3951 alice_group.apply_pending_commit().await.unwrap();
3952
3953 let received_by_alice = alice_group.process_incoming_message(msg).await.unwrap();
3954 assert_matches!(
3955 received_by_alice,
3956 ReceivedMessage::ApplicationMessage(ApplicationMessageDescription { unauthenticated_key_generation, .. })
3957 if unauthenticated_key_generation.unwrap().to_be_bytes() == authn_key_gen
3958 );
3959 }
3960
3961 #[cfg(all(
3962 feature = "export_key_generation",
3963 feature = "private_message",
3964 feature = "secret_tree_access",
3965 ))]
3966 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3967 async fn peek_next_key_generation() {
3968 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3969 let (mut bob_group, _) = alice_group.join("bob").await;
3970
3971 assert_eq!(bob_group.peek_next_key_generation().unwrap(), 0);
3972 assert_eq!(bob_group.peek_next_key_generation().unwrap(), 0);
3973
3974 let bob_msg = b"I'm Bob";
3975 bob_group
3976 .encrypt_application_message(bob_msg, vec![])
3977 .await
3978 .unwrap();
3979
3980 assert_eq!(bob_group.peek_next_key_generation().unwrap(), 1);
3981 bob_group
3982 .encrypt_application_message(bob_msg, vec![])
3983 .await
3984 .unwrap();
3985
3986 bob_group
3987 .encrypt_application_message(bob_msg, vec![])
3988 .await
3989 .unwrap();
3990
3991 assert_eq!(bob_group.peek_next_key_generation().unwrap(), 3);
3992 }
3993
3994 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
3995 async fn members_of_a_group_have_identical_authentication_secrets() {
3996 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
3997 let (bob_group, _) = alice_group.join("bob").await;
3998
3999 assert_eq!(
4000 alice_group.epoch_authenticator().unwrap(),
4001 bob_group.epoch_authenticator().unwrap()
4002 );
4003 }
4004
4005 #[cfg(feature = "private_message")]
4006 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4007 async fn member_cannot_decrypt_same_message_twice() {
4008 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
4009 let (mut bob_group, _) = alice_group.join("bob").await;
4010
4011 let message = alice_group
4012 .encrypt_application_message(b"foobar", Vec::new())
4013 .await
4014 .unwrap();
4015
4016 let received_message = bob_group
4017 .process_incoming_message(message.clone())
4018 .await
4019 .unwrap();
4020
4021 assert_matches!(
4022 received_message,
4023 ReceivedMessage::ApplicationMessage(m) if m.data() == b"foobar"
4024 );
4025
4026 let res = bob_group.process_incoming_message(message).await;
4027
4028 assert_matches!(res, Err(MlsError::KeyMissing(0)));
4029 }
4030
4031 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4032 async fn removing_requirements_allows_to_add() {
4033 let mut alice_group = test_group_custom(
4034 TEST_PROTOCOL_VERSION,
4035 TEST_CIPHER_SUITE,
4036 vec![17.into()],
4037 None,
4038 None,
4039 )
4040 .await;
4041
4042 alice_group
4043 .commit_builder()
4044 .set_group_context_ext(
4045 vec![RequiredCapabilitiesExt {
4046 extensions: vec![17.into()],
4047 ..Default::default()
4048 }
4049 .into_extension()
4050 .unwrap()]
4051 .try_into()
4052 .unwrap(),
4053 )
4054 .unwrap()
4055 .build()
4056 .await
4057 .unwrap();
4058
4059 alice_group.process_pending_commit().await.unwrap();
4060
4061 let test_key_package =
4062 test_key_package(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
4063
4064 let test_key_package = MlsMessage::new(
4065 TEST_PROTOCOL_VERSION,
4066 MlsMessagePayload::KeyPackage(test_key_package),
4067 );
4068
4069 alice_group
4070 .commit_builder()
4071 .add_member(test_key_package.clone())
4072 .unwrap()
4073 .set_group_context_ext(Default::default())
4074 .unwrap()
4075 .build()
4076 .await
4077 .unwrap();
4078
4079 let CommitEffect::NewEpoch(new_epoch) =
4080 alice_group.process_pending_commit().await.unwrap().effect
4081 else {
4082 panic!("unexpected commit effect")
4083 };
4084
4085 assert_eq!(new_epoch.applied_proposals.len(), 2);
4086
4087 assert_matches!(
4088 new_epoch.applied_proposals[0],
4089 ProposalInfo { proposal: Proposal::Add(ref add), .. }
4090 if add.key_package == test_key_package.into_key_package().unwrap()
4091 );
4092
4093 assert_eq!(alice_group.roster().members_iter().count(), 2);
4094 }
4095
4096 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4097 async fn commit_leaf_wrong_source() {
4098 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 3).await;
4100
4101 groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
4102 leaf.leaf_node_source = LeafNodeSource::Update;
4103 Some(sk.clone())
4104 };
4105
4106 let commit_output = groups[0].commit(vec![]).await.unwrap();
4107
4108 let res = groups[2]
4109 .process_message(commit_output.commit_message)
4110 .await;
4111
4112 assert_matches!(res, Err(MlsError::InvalidLeafNodeSource));
4113 }
4114
4115 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4116 async fn commit_leaf_same_hpke_key() {
4117 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 3).await;
4120
4121 groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
4123 leaf.public_key = get_test_25519_key(1u8);
4124 Some(sk.clone())
4125 };
4126
4127 let commit_output = groups[0].commit(vec![]).await.unwrap();
4128 groups[0].process_pending_commit().await.unwrap();
4129 groups[2]
4130 .process_message(commit_output.commit_message)
4131 .await
4132 .unwrap();
4133
4134 let commit_output = groups[0].commit(vec![]).await.unwrap();
4136
4137 let res = groups[2]
4138 .process_message(commit_output.commit_message)
4139 .await;
4140
4141 assert_matches!(res, Err(MlsError::SameHpkeKey(0)));
4142 }
4143
4144 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4145 async fn commit_leaf_duplicate_hpke_key() {
4146 if TEST_CIPHER_SUITE != CipherSuite::CURVE25519_AES128
4149 && TEST_CIPHER_SUITE != CipherSuite::CURVE25519_CHACHA
4150 {
4151 return;
4152 }
4153
4154 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 10).await;
4155
4156 groups[1].commit_modifiers.modify_leaf = |leaf, sk| {
4158 leaf.public_key = get_test_25519_key(1u8);
4159 Some(sk.clone())
4160 };
4161
4162 let commit_output = groups.get_mut(1).unwrap().commit(vec![]).await.unwrap();
4163
4164 process_commit(&mut groups, commit_output.commit_message, 1).await;
4165
4166 groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
4168 leaf.public_key = get_test_25519_key(1u8);
4169 Some(sk.clone())
4170 };
4171
4172 let commit_output = groups[0].commit(vec![]).await.unwrap();
4173
4174 let res = groups[7]
4175 .process_message(commit_output.commit_message)
4176 .await;
4177
4178 assert_matches!(res, Err(MlsError::DuplicateLeafData(_)));
4179 }
4180
4181 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4182 async fn commit_leaf_duplicate_signature_key() {
4183 if TEST_CIPHER_SUITE != CipherSuite::CURVE25519_AES128
4186 && TEST_CIPHER_SUITE != CipherSuite::CURVE25519_CHACHA
4187 {
4188 return;
4189 }
4190
4191 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 10).await;
4192
4193 groups[1].commit_modifiers.modify_leaf = |leaf, _| {
4195 let sk = hex!(
4196 "3468b4c890255c983e3d5cbf5cb64c1ef7f6433a518f2f3151d6672f839a06ebcad4fc381fe61822af45135c82921a348e6f46643d66ddefc70483565433714b"
4197 )
4198 .into();
4199
4200 leaf.signing_identity.signature_key =
4201 hex!("cad4fc381fe61822af45135c82921a348e6f46643d66ddefc70483565433714b").into();
4202
4203 Some(sk)
4204 };
4205
4206 let commit_output = groups.get_mut(1).unwrap().commit(vec![]).await.unwrap();
4207
4208 process_commit(&mut groups, commit_output.commit_message, 1).await;
4209
4210 groups[0].commit_modifiers.modify_leaf = |leaf, _| {
4212 let sk = hex!(
4213 "3468b4c890255c983e3d5cbf5cb64c1ef7f6433a518f2f3151d6672f839a06ebcad4fc381fe61822af45135c82921a348e6f46643d66ddefc70483565433714b"
4214 )
4215 .into();
4216
4217 leaf.signing_identity.signature_key =
4218 hex!("cad4fc381fe61822af45135c82921a348e6f46643d66ddefc70483565433714b").into();
4219
4220 Some(sk)
4221 };
4222
4223 let commit_output = groups[0].commit(vec![]).await.unwrap();
4224
4225 let res = groups[7]
4226 .process_message(commit_output.commit_message)
4227 .await;
4228
4229 assert_matches!(res, Err(MlsError::DuplicateLeafData(_)));
4230 }
4231
4232 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4233 async fn commit_leaf_incorrect_signature() {
4234 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 3).await;
4235
4236 groups[0].commit_modifiers.modify_leaf = |leaf, _| {
4237 leaf.signature[0] ^= 1;
4238 None
4239 };
4240
4241 let commit_output = groups[0].commit(vec![]).await.unwrap();
4242
4243 let res = groups[2]
4244 .process_message(commit_output.commit_message)
4245 .await;
4246
4247 assert_matches!(res, Err(MlsError::InvalidSignature));
4248 }
4249
4250 #[cfg(not(target_arch = "wasm32"))]
4251 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4252 async fn commit_leaf_not_supporting_used_context_extension() {
4253 const EXT_TYPE: ExtensionType = ExtensionType::new(999);
4254
4255 let extension = Extension::new(EXT_TYPE, vec![]);
4257
4258 let mut groups =
4259 get_test_groups_with_features(3, vec![extension].into(), Default::default()).await;
4260
4261 groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
4262 leaf.capabilities = get_test_capabilities();
4263 Some(sk.clone())
4264 };
4265
4266 let commit_output = groups[0].commit(vec![]).await.unwrap();
4267
4268 let res = groups[1]
4269 .process_incoming_message(commit_output.commit_message)
4270 .await;
4271
4272 assert_matches!(res, Err(MlsError::UnsupportedGroupExtension(EXT_TYPE)));
4273 }
4274
4275 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4276 async fn commit_leaf_not_supporting_required_extension() {
4277 let extension = RequiredCapabilitiesExt {
4280 extensions: vec![999.into()],
4281 proposals: vec![],
4282 credentials: vec![],
4283 };
4284
4285 let extensions = vec![extension.into_extension().unwrap()];
4286 let mut groups =
4287 get_test_groups_with_features(3, extensions.into(), Default::default()).await;
4288
4289 groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
4290 leaf.capabilities = Capabilities::default();
4291 Some(sk.clone())
4292 };
4293
4294 let commit_output = groups[0].commit(vec![]).await.unwrap();
4295
4296 let res = groups[2]
4297 .process_incoming_message(commit_output.commit_message)
4298 .await;
4299
4300 assert!(res.is_err());
4301 }
4302
4303 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4304 async fn commit_leaf_has_unsupported_credential() {
4305 let mut groups =
4307 get_test_groups_with_features(3, Default::default(), Default::default()).await;
4308
4309 for group in groups.iter_mut() {
4310 group.config.0.identity_provider.allow_any_custom = true;
4311 }
4312
4313 groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
4314 leaf.signing_identity.credential = Credential::Custom(CustomCredential::new(
4315 CredentialType::new(43),
4316 leaf.signing_identity
4317 .credential
4318 .as_basic()
4319 .unwrap()
4320 .identifier
4321 .to_vec(),
4322 ));
4323
4324 Some(sk.clone())
4325 };
4326
4327 let commit_output = groups[0].commit(vec![]).await.unwrap();
4328
4329 let res = groups[2]
4330 .process_incoming_message(commit_output.commit_message)
4331 .await;
4332
4333 assert_matches!(res, Err(MlsError::CredentialTypeOfNewLeafIsUnsupported));
4334 }
4335
4336 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4337 async fn commit_leaf_not_supporting_credential_used_in_another_leaf() {
4338 let mut groups =
4341 get_test_groups_with_features(3, Default::default(), Default::default()).await;
4342
4343 groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
4344 leaf.capabilities.credentials = vec![2.into()];
4345 Some(sk.clone())
4346 };
4347
4348 let commit_output = groups[0].commit(vec![]).await.unwrap();
4349
4350 let res = groups[2]
4351 .process_incoming_message(commit_output.commit_message)
4352 .await;
4353
4354 assert_matches!(res, Err(MlsError::InUseCredentialTypeUnsupportedByNewLeaf));
4355 }
4356
4357 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4358 async fn commit_leaf_not_supporting_required_credential() {
4359 let extension = RequiredCapabilitiesExt {
4362 extensions: vec![],
4363 proposals: vec![],
4364 credentials: vec![1.into()],
4365 };
4366
4367 let extensions = vec![extension.into_extension().unwrap()];
4368 let mut groups =
4369 get_test_groups_with_features(3, extensions.into(), Default::default()).await;
4370
4371 groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
4372 leaf.capabilities.credentials = vec![2.into()];
4373 Some(sk.clone())
4374 };
4375
4376 let commit_output = groups[0].commit(vec![]).await.unwrap();
4377
4378 let res = groups[2]
4379 .process_incoming_message(commit_output.commit_message)
4380 .await;
4381
4382 assert_matches!(res, Err(MlsError::RequiredCredentialNotFound(_)));
4383 }
4384
4385 #[cfg(feature = "by_ref_proposal")]
4386 #[cfg(not(target_arch = "wasm32"))]
4387 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
4388 async fn make_x509_external_senders_ext() -> ExternalSendersExt {
4389 let (_, ext_sender_pk) = test_cipher_suite_provider(TEST_CIPHER_SUITE)
4390 .signature_key_generate()
4391 .await
4392 .unwrap();
4393
4394 let ext_sender_id = SigningIdentity {
4395 signature_key: ext_sender_pk,
4396 credential: Credential::X509(CertificateChain::from(vec![random_bytes(32)])),
4397 };
4398
4399 ExternalSendersExt::new(vec![ext_sender_id])
4400 }
4401
4402 #[cfg(feature = "by_ref_proposal")]
4403 #[cfg(not(target_arch = "wasm32"))]
4404 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4405 async fn commit_leaf_not_supporting_external_sender_credential_leads_to_rejected_commit() {
4406 let ext_senders = make_x509_external_senders_ext()
4407 .await
4408 .into_extension()
4409 .unwrap();
4410
4411 let mut alice = ClientBuilder::new()
4412 .crypto_provider(TestCryptoProvider::new())
4413 .identity_provider(
4414 BasicWithCustomProvider::default().with_credential_type(CredentialType::X509),
4415 )
4416 .with_random_signing_identity("alice", TEST_CIPHER_SUITE)
4417 .await
4418 .build()
4419 .create_group(vec![ext_senders].into(), Default::default(), None)
4420 .await
4421 .unwrap();
4422
4423 let bob = ClientBuilder::new()
4424 .crypto_provider(TestCryptoProvider::new())
4425 .identity_provider(
4426 BasicWithCustomProvider::default().with_credential_type(CredentialType::X509),
4427 )
4428 .with_random_signing_identity("bob", TEST_CIPHER_SUITE)
4429 .await
4430 .build();
4431
4432 let kp = bob
4433 .generate_key_package_message(Default::default(), Default::default(), None)
4434 .await
4435 .unwrap();
4436
4437 let commit = alice
4438 .commit_builder()
4439 .add_member(kp)
4440 .unwrap()
4441 .build()
4442 .await
4443 .unwrap();
4444
4445 let (mut bob, _) = bob
4446 .join_group(None, &commit.welcome_messages[0], None)
4447 .await
4448 .unwrap();
4449
4450 alice.apply_pending_commit().await.unwrap();
4451
4452 alice.commit_modifiers.modify_leaf = |leaf, sk| {
4454 leaf.capabilities.credentials = vec![CredentialType::BASIC];
4455 Some(sk.clone())
4456 };
4457
4458 let commit = alice.commit(vec![]).await.unwrap();
4459 let res = bob.process_incoming_message(commit.commit_message).await;
4460
4461 assert_matches!(
4462 res,
4463 Err(MlsError::RequiredCredentialNotFound(CredentialType::X509))
4464 );
4465 }
4466
4467 #[cfg(feature = "by_ref_proposal")]
4468 #[cfg(not(target_arch = "wasm32"))]
4469 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4470 async fn node_not_supporting_external_sender_credential_cannot_join_group() {
4471 let ext_senders = make_x509_external_senders_ext()
4472 .await
4473 .into_extension()
4474 .unwrap();
4475
4476 let mut alice = ClientBuilder::new()
4477 .crypto_provider(TestCryptoProvider::new())
4478 .identity_provider(
4479 BasicWithCustomProvider::default().with_credential_type(CredentialType::X509),
4480 )
4481 .with_random_signing_identity("alice", TEST_CIPHER_SUITE)
4482 .await
4483 .build()
4484 .create_group(
4485 core::iter::once(ext_senders).collect(),
4486 Default::default(),
4487 None,
4488 )
4489 .await
4490 .unwrap();
4491
4492 let (_, bob_key_pkg) =
4493 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
4494
4495 let commit = alice
4496 .commit_builder()
4497 .add_member(bob_key_pkg)
4498 .unwrap()
4499 .build()
4500 .await;
4501
4502 assert_matches!(
4503 commit,
4504 Err(MlsError::RequiredCredentialNotFound(CredentialType::X509))
4505 );
4506 }
4507
4508 #[cfg(feature = "by_ref_proposal")]
4509 #[cfg(not(target_arch = "wasm32"))]
4510 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4511 async fn external_senders_extension_is_rejected_if_member_does_not_support_credential_type() {
4512 let mut alice = ClientBuilder::new()
4513 .crypto_provider(TestCryptoProvider::new())
4514 .identity_provider(
4515 BasicWithCustomProvider::default().with_credential_type(CredentialType::X509),
4516 )
4517 .with_random_signing_identity("alice", TEST_CIPHER_SUITE)
4518 .await
4519 .build()
4520 .create_group(Default::default(), Default::default(), None)
4521 .await
4522 .unwrap();
4523
4524 let (_, bob_key_pkg) =
4525 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
4526
4527 alice
4528 .commit_builder()
4529 .add_member(bob_key_pkg)
4530 .unwrap()
4531 .build()
4532 .await
4533 .unwrap();
4534
4535 alice.apply_pending_commit().await.unwrap();
4536 assert_eq!(alice.roster().members_iter().count(), 2);
4537
4538 let ext_senders = make_x509_external_senders_ext()
4539 .await
4540 .into_extension()
4541 .unwrap();
4542
4543 let res = alice
4544 .commit_builder()
4545 .set_group_context_ext(core::iter::once(ext_senders).collect())
4546 .unwrap()
4547 .build()
4548 .await;
4549
4550 assert_matches!(
4551 res,
4552 Err(MlsError::RequiredCredentialNotFound(CredentialType::X509))
4553 );
4554 }
4555
4556 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4561 async fn committing_degenerate_path_succeeds() {
4562 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 10).await;
4563
4564 groups[0].commit_modifiers.modify_tree = |tree: &mut TreeKemPublic| {
4565 tree.update_node(get_test_25519_key(1u8), 1).unwrap();
4566 tree.update_node(get_test_25519_key(1u8), 3).unwrap();
4567 };
4568
4569 groups[0].commit_modifiers.modify_leaf = |leaf, sk| {
4570 leaf.public_key = get_test_25519_key(1u8);
4571 Some(sk.clone())
4572 };
4573
4574 let commit_output = groups[0].commit(vec![]).await.unwrap();
4575
4576 let res = groups[7]
4577 .process_message(commit_output.commit_message)
4578 .await;
4579
4580 assert!(res.is_ok());
4581 }
4582
4583 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4584 async fn inserting_key_in_filtered_node_fails() {
4585 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 10).await;
4586
4587 let commit_output = groups[0]
4588 .commit_builder()
4589 .remove_member(1)
4590 .unwrap()
4591 .build()
4592 .await
4593 .unwrap();
4594
4595 groups[0].process_pending_commit().await.unwrap();
4596
4597 for group in groups.iter_mut().skip(2) {
4598 group
4599 .process_message(commit_output.commit_message.clone())
4600 .await
4601 .unwrap();
4602 }
4603
4604 groups[0].commit_modifiers.modify_tree = |tree: &mut TreeKemPublic| {
4605 tree.update_node(get_test_25519_key(1u8), 1).unwrap();
4606 };
4607
4608 groups[0].commit_modifiers.modify_path = |path: Vec<UpdatePathNode>| {
4609 let mut path = path;
4610 let mut node = path[0].clone();
4611 node.public_key = get_test_25519_key(1u8);
4612 path.insert(0, node);
4613 path
4614 };
4615
4616 let commit_output = groups[0].commit(vec![]).await.unwrap();
4617
4618 let res = groups[7]
4619 .process_message(commit_output.commit_message)
4620 .await;
4621
4622 assert_matches!(res, Err(MlsError::WrongPathLen));
4624 }
4625
4626 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4627 async fn commit_with_too_short_path_fails() {
4628 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 10).await;
4629
4630 let commit_output = groups[0]
4631 .commit_builder()
4632 .remove_member(1)
4633 .unwrap()
4634 .build()
4635 .await
4636 .unwrap();
4637
4638 groups[0].process_pending_commit().await.unwrap();
4639
4640 for group in groups.iter_mut().skip(2) {
4641 group
4642 .process_message(commit_output.commit_message.clone())
4643 .await
4644 .unwrap();
4645 }
4646
4647 groups[0].commit_modifiers.modify_path = |path: Vec<UpdatePathNode>| {
4648 let mut path = path;
4649 path.pop();
4650 path
4651 };
4652
4653 let commit_output = groups[0].commit(vec![]).await.unwrap();
4654
4655 let res = groups[7]
4656 .process_message(commit_output.commit_message)
4657 .await;
4658
4659 assert!(res.is_err());
4660 }
4661
4662 #[cfg(feature = "by_ref_proposal")]
4663 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4664 async fn update_proposal_can_change_credential() {
4665 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 3).await;
4666 let (identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"member").await;
4667
4668 let update = groups[0]
4669 .propose_update_with_identity(secret_key, identity.clone(), vec![])
4670 .await
4671 .unwrap();
4672
4673 groups[1].process_message(update).await.unwrap();
4674 let commit_output = groups[1].commit(vec![]).await.unwrap();
4675
4676 groups[1].process_pending_commit().await.unwrap();
4678 let new_member = groups[1].roster().member_with_index(0).unwrap();
4679
4680 assert_eq!(
4681 new_member.signing_identity.credential,
4682 get_test_basic_credential(b"member".to_vec())
4683 );
4684
4685 assert_eq!(
4686 new_member.signing_identity.signature_key,
4687 identity.signature_key
4688 );
4689
4690 groups[0]
4692 .process_message(commit_output.commit_message)
4693 .await
4694 .unwrap();
4695 let new_member = groups[0].roster().member_with_index(0).unwrap();
4696
4697 assert_eq!(
4698 new_member.signing_identity.credential,
4699 get_test_basic_credential(b"member".to_vec())
4700 );
4701
4702 assert_eq!(
4703 new_member.signing_identity.signature_key,
4704 identity.signature_key
4705 );
4706 }
4707
4708 #[cfg(feature = "by_ref_proposal")]
4709 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4710 async fn receiving_commit_with_old_adds_fails() {
4711 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 2).await;
4712
4713 let key_package =
4714 test_key_package_message(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "foobar").await;
4715
4716 let proposal = groups[0].propose_add(key_package, vec![]).await.unwrap();
4717
4718 let commit = groups[0].commit(vec![]).await.unwrap().commit_message;
4719
4720 let future_time = MlsTime::now().seconds_since_epoch() + 10 * 365 * 24 * 3600;
4722
4723 let future_time =
4724 MlsTime::from_duration_since_epoch(core::time::Duration::from_secs(future_time));
4725
4726 groups[1].process_incoming_message(proposal).await.unwrap();
4727 let res = groups[1]
4728 .process_incoming_message_with_time(commit, future_time)
4729 .await;
4730
4731 assert_matches!(
4732 res,
4733 Err(MlsError::InvalidLifetime { timestamp, .. })
4734 if timestamp == future_time
4735 );
4736 }
4737
4738 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4739 async fn can_process_commit_from_self_with_time() {
4740 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
4741
4742 let commit = alice.commit(Vec::new()).await.unwrap();
4743 assert!(alice.has_pending_commit());
4744
4745 alice
4746 .process_incoming_message_with_time(commit.commit_message, MlsTime::now())
4747 .await
4748 .unwrap();
4749 }
4750
4751 #[cfg(feature = "custom_proposal")]
4752 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
4753 async fn custom_proposal_setup() -> (TestGroup, TestGroup) {
4754 let mut alice = test_group_custom_config(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, |b| {
4755 b.custom_proposal_type(TEST_CUSTOM_PROPOSAL_TYPE)
4756 })
4757 .await;
4758
4759 let (bob, _) = alice
4760 .join_with_custom_config("bob", true, |c| {
4761 c.0.settings
4762 .custom_proposal_types
4763 .push(TEST_CUSTOM_PROPOSAL_TYPE)
4764 })
4765 .await
4766 .unwrap();
4767
4768 (alice, bob)
4769 }
4770
4771 #[cfg(all(
4772 feature = "by_ref_proposal",
4773 feature = "custom_proposal",
4774 feature = "self_remove_proposal"
4775 ))]
4776 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
4777 async fn self_remove_group_setup() -> (TestGroup, TestGroup) {
4778 let mut alice = test_group_custom_config(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, |b| {
4779 b.custom_proposal_type(ProposalType::SELF_REMOVE)
4780 })
4781 .await;
4782
4783 let (bob, _) = alice
4784 .join_with_custom_config("bob", true, |c| {
4785 c.0.settings
4786 .custom_proposal_types
4787 .push(ProposalType::SELF_REMOVE)
4788 })
4789 .await
4790 .unwrap();
4791
4792 (alice, bob)
4793 }
4794
4795 #[cfg(feature = "custom_proposal")]
4796 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4797 async fn custom_proposal_by_value() {
4798 let (mut alice, mut bob) = custom_proposal_setup().await;
4799
4800 let custom_proposal = CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![0, 1, 2]);
4801
4802 let commit = alice
4803 .commit_builder()
4804 .custom_proposal(custom_proposal.clone())
4805 .build()
4806 .await
4807 .unwrap()
4808 .commit_message;
4809
4810 let ReceivedMessage::Commit(CommitMessageDescription {
4811 effect: CommitEffect::NewEpoch(new_epoch),
4812 ..
4813 }) = bob.process_incoming_message(commit).await.unwrap()
4814 else {
4815 panic!("unexpected commit effect");
4816 };
4817
4818 assert_eq!(new_epoch.applied_proposals.len(), 1);
4819
4820 assert_eq!(
4821 new_epoch.applied_proposals[0].proposal,
4822 Proposal::Custom(custom_proposal)
4823 );
4824 }
4825
4826 #[cfg(feature = "custom_proposal")]
4827 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4828 async fn custom_proposal_by_reference() {
4829 let (mut alice, mut bob) = custom_proposal_setup().await;
4830
4831 let custom_proposal = CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![0, 1, 2]);
4832
4833 let proposal = alice
4834 .propose_custom(custom_proposal.clone(), vec![])
4835 .await
4836 .unwrap();
4837
4838 let recv_prop = bob.process_incoming_message(proposal).await.unwrap();
4839
4840 assert_matches!(recv_prop, ReceivedMessage::Proposal(ProposalMessageDescription { proposal: Proposal::Custom(c), ..})
4841 if c == custom_proposal);
4842
4843 let commit = bob.commit(vec![]).await.unwrap().commit_message;
4844
4845 let ReceivedMessage::Commit(CommitMessageDescription {
4846 effect: CommitEffect::NewEpoch(new_epoch),
4847 ..
4848 }) = bob.process_incoming_message(commit).await.unwrap()
4849 else {
4850 panic!("unexpected commit effect");
4851 };
4852
4853 assert_eq!(new_epoch.applied_proposals.len(), 1);
4854
4855 assert_eq!(
4856 new_epoch.applied_proposals[0].proposal,
4857 Proposal::Custom(custom_proposal)
4858 );
4859 }
4860
4861 #[cfg(all(feature = "custom_proposal", feature = "gsma_rcs_e2ee_feature"))]
4862 #[derive(MlsSize, MlsDecode, MlsEncode, Debug, PartialEq)]
4863 struct RcsSignature {}
4864 #[cfg(all(feature = "custom_proposal", feature = "gsma_rcs_e2ee_feature"))]
4865 impl MlsCustomProposal for RcsSignature {
4866 fn proposal_type() -> ProposalType {
4867 ProposalType::RCS_SIGNATURE
4868 }
4869
4870 fn to_custom_proposal(&self) -> Result<CustomProposal, mls_rs_codec::Error> {
4871 Ok(CustomProposal::new(Self::proposal_type(), Vec::new()))
4872 }
4873
4874 fn from_custom_proposal(proposal: &CustomProposal) -> Result<Self, mls_rs_codec::Error> {
4875 if proposal.proposal_type() != Self::proposal_type() {
4876 return Err(mls_rs_codec::Error::Custom(4));
4877 }
4878
4879 Ok(Self {})
4880 }
4881 }
4882
4883 #[cfg(all(feature = "custom_proposal", feature = "gsma_rcs_e2ee_feature"))]
4884 #[derive(MlsSize, MlsDecode, MlsEncode, Debug, PartialEq)]
4885 struct RcsServerRemove {
4886 to_remove: u32,
4887 }
4888
4889 #[cfg(all(feature = "custom_proposal", feature = "gsma_rcs_e2ee_feature"))]
4890 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4891 async fn custom_proposal_custom_data_encoding_rcs_signature() {
4892 let mut alice = test_group_custom_config(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, |b| {
4893 b.custom_proposal_type(ProposalType::RCS_SIGNATURE)
4894 })
4895 .await;
4896
4897 let (mut bob, _) = alice
4898 .join_with_custom_config("bob", true, |c| {
4899 c.0.settings
4900 .custom_proposal_types
4901 .push(ProposalType::RCS_SIGNATURE)
4902 })
4903 .await
4904 .unwrap();
4905
4906 let custom_proposal = RcsSignature {};
4907
4908 let proposal = alice
4909 .propose_custom(custom_proposal.to_custom_proposal().unwrap(), vec![])
4910 .await
4911 .unwrap();
4912
4913 let recv_prop = bob.process_incoming_message(proposal).await.unwrap();
4914
4915 assert_matches!(recv_prop, ReceivedMessage::Proposal(ProposalMessageDescription { proposal: Proposal::Custom(c), ..})
4916 if c == custom_proposal.to_custom_proposal().unwrap());
4917
4918 let commit = bob.commit(vec![]).await.unwrap().commit_message;
4919
4920 let ReceivedMessage::Commit(CommitMessageDescription {
4921 effect: CommitEffect::NewEpoch(new_epoch),
4922 ..
4923 }) = bob.process_incoming_message(commit).await.unwrap()
4924 else {
4925 panic!("unexpected commit effect");
4926 };
4927
4928 assert_eq!(new_epoch.applied_proposals.len(), 1);
4929
4930 let Proposal::Custom(ref c) = new_epoch.applied_proposals[0].proposal else {
4931 panic!("unexpected non-custom proposal");
4932 };
4933 assert_eq!(
4934 RcsSignature::from_custom_proposal(c).unwrap(),
4935 custom_proposal
4936 );
4937 }
4938
4939 #[cfg(all(feature = "custom_proposal", feature = "gsma_rcs_e2ee_feature"))]
4940 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4941 async fn custom_proposal_custom_data_encoding_rcs_server_remove() {
4942 let mut alice = test_group_custom_config(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, |b| {
4943 b.custom_proposal_type(ProposalType::RCS_SERVER_REMOVE)
4944 })
4945 .await;
4946
4947 let (mut bob, _) = alice
4948 .join_with_custom_config("bob", true, |c| {
4949 c.0.settings
4950 .custom_proposal_types
4951 .push(ProposalType::RCS_SERVER_REMOVE)
4952 })
4953 .await
4954 .unwrap();
4955
4956 let server_remove_proposal = RcsServerRemove { to_remove: 1 };
4957 let custom_proposal = CustomProposal::new(
4959 ProposalType::RCS_SERVER_REMOVE,
4960 server_remove_proposal.mls_encode_to_vec().unwrap(),
4961 );
4962
4963 let proposal = alice
4964 .propose_custom(custom_proposal.clone(), vec![])
4965 .await
4966 .unwrap();
4967
4968 let recv_prop = bob.process_incoming_message(proposal).await.unwrap();
4969
4970 assert_matches!(recv_prop, ReceivedMessage::Proposal(ProposalMessageDescription { proposal: Proposal::Custom(c), ..})
4971 if c == custom_proposal);
4972
4973 let commit = bob.commit(vec![]).await.unwrap().commit_message;
4974
4975 let ReceivedMessage::Commit(CommitMessageDescription {
4976 effect: CommitEffect::NewEpoch(new_epoch),
4977 ..
4978 }) = bob.process_incoming_message(commit).await.unwrap()
4979 else {
4980 panic!("unexpected commit effect");
4981 };
4982
4983 assert_eq!(new_epoch.applied_proposals.len(), 1);
4984
4985 let Proposal::Custom(ref c) = new_epoch.applied_proposals[0].proposal else {
4986 panic!("unexpected non-custom proposal");
4987 };
4988 let reader = c.data().to_vec();
4989 let server_remove_decoded = RcsServerRemove::mls_decode(&mut reader.as_slice()).unwrap();
4990 assert_eq!(server_remove_decoded, server_remove_proposal);
4991 }
4992
4993 #[cfg(feature = "psk")]
4994 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
4995 async fn can_join_with_psk() {
4996 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
4997
4998 let (bob, key_pkg) =
4999 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
5000
5001 let psk_id = ExternalPskId::new(vec![0]);
5002 let psk = PreSharedKey::from(vec![0]);
5003
5004 alice
5005 .config
5006 .secret_store()
5007 .insert(psk_id.clone(), psk.clone());
5008
5009 bob.config.secret_store().insert(psk_id.clone(), psk);
5010
5011 let commit = alice
5012 .commit_builder()
5013 .add_member(key_pkg)
5014 .unwrap()
5015 .add_external_psk(psk_id)
5016 .unwrap()
5017 .build()
5018 .await
5019 .unwrap();
5020
5021 bob.join_group(None, &commit.welcome_messages[0], None)
5022 .await
5023 .unwrap();
5024 }
5025
5026 #[cfg(feature = "by_ref_proposal")]
5027 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5028 async fn invalid_update_does_not_prevent_other_updates() {
5029 const EXTENSION_TYPE: ExtensionType = ExtensionType::new(33);
5030
5031 let group_extensions = ExtensionList::from(vec![RequiredCapabilitiesExt {
5032 extensions: vec![EXTENSION_TYPE],
5033 ..Default::default()
5034 }
5035 .into_extension()
5036 .unwrap()]);
5037
5038 let mut alice = TestClientBuilder::new_for_test()
5040 .with_random_signing_identity("alice", TEST_CIPHER_SUITE)
5041 .await
5042 .extension_type(EXTENSION_TYPE)
5043 .build()
5044 .create_group(group_extensions.clone(), Default::default(), None)
5045 .await
5046 .unwrap();
5047
5048 let (bob_signing_identity, bob_secret_key) =
5049 get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;
5050
5051 let bob_client = TestClientBuilder::new_for_test()
5052 .signing_identity(
5053 bob_signing_identity.clone(),
5054 bob_secret_key.clone(),
5055 TEST_CIPHER_SUITE,
5056 )
5057 .extension_type(EXTENSION_TYPE)
5058 .build();
5059
5060 let carol_client = TestClientBuilder::new_for_test()
5061 .with_random_signing_identity("carol", TEST_CIPHER_SUITE)
5062 .await
5063 .extension_type(EXTENSION_TYPE)
5064 .build();
5065
5066 let dave_client = TestClientBuilder::new_for_test()
5067 .with_random_signing_identity("dave", TEST_CIPHER_SUITE)
5068 .await
5069 .extension_type(EXTENSION_TYPE)
5070 .build();
5071
5072 let commit = alice
5074 .commit_builder()
5075 .add_member(
5076 bob_client
5077 .generate_key_package_message(Default::default(), Default::default(), None)
5078 .await
5079 .unwrap(),
5080 )
5081 .unwrap()
5082 .add_member(
5083 carol_client
5084 .generate_key_package_message(Default::default(), Default::default(), None)
5085 .await
5086 .unwrap(),
5087 )
5088 .unwrap()
5089 .add_member(
5090 dave_client
5091 .generate_key_package_message(Default::default(), Default::default(), None)
5092 .await
5093 .unwrap(),
5094 )
5095 .unwrap()
5096 .build()
5097 .await
5098 .unwrap();
5099
5100 alice.apply_pending_commit().await.unwrap();
5101
5102 let mut bob = bob_client
5103 .join_group(None, &commit.welcome_messages[0], None)
5104 .await
5105 .unwrap()
5106 .0;
5107
5108 bob.write_to_storage().await.unwrap();
5109
5110 let mut bob = TestClientBuilder::new_for_test()
5113 .signing_identity(bob_signing_identity, bob_secret_key, TEST_CIPHER_SUITE)
5114 .key_package_repo(bob.config.key_package_repo())
5115 .group_state_storage(bob.config.group_state_storage())
5116 .build()
5117 .load_group(alice.group_id())
5118 .await
5119 .unwrap();
5120
5121 let mut carol = carol_client
5122 .join_group(None, &commit.welcome_messages[0], None)
5123 .await
5124 .unwrap()
5125 .0;
5126
5127 let mut dave = dave_client
5128 .join_group(None, &commit.welcome_messages[0], None)
5129 .await
5130 .unwrap()
5131 .0;
5132
5133 let bob_update = bob.propose_update(Vec::new()).await.unwrap();
5135 let carol_update = carol.propose_update(Vec::new()).await.unwrap();
5136 let dave_update = dave.propose_update(Vec::new()).await.unwrap();
5137
5138 alice.process_incoming_message(bob_update).await.unwrap();
5140 alice.process_incoming_message(carol_update).await.unwrap();
5141 alice.process_incoming_message(dave_update).await.unwrap();
5142
5143 alice.commit(Vec::new()).await.unwrap();
5145
5146 let CommitEffect::NewEpoch(new_epoch) = alice.apply_pending_commit().await.unwrap().effect
5147 else {
5148 panic!("unexpected commit effect");
5149 };
5150
5151 let find_update_for = |id: &str| {
5152 new_epoch
5153 .applied_proposals
5154 .iter()
5155 .filter_map(|p| match p.proposal {
5156 Proposal::Update(ref u) => u.signing_identity().credential.as_basic(),
5157 _ => None,
5158 })
5159 .any(|c| c.identifier == id.as_bytes())
5160 };
5161
5162 assert!(find_update_for("carol"));
5164 assert!(find_update_for("dave"));
5165
5166 assert!(!find_update_for("bob"));
5168
5169 let all_members_are_in = alice
5171 .roster()
5172 .members_iter()
5173 .zip(["alice", "bob", "carol", "dave"])
5174 .all(|(member, id)| {
5175 member
5176 .signing_identity
5177 .credential
5178 .as_basic()
5179 .unwrap()
5180 .identifier
5181 == id.as_bytes()
5182 });
5183
5184 assert!(all_members_are_in);
5185 }
5186
5187 #[cfg(all(
5188 feature = "by_ref_proposal",
5189 feature = "custom_proposal",
5190 feature = "self_remove_proposal"
5191 ))]
5192 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5193 async fn client_cannot_propose_self_remove_twice() {
5194 let mut alice = TestClientBuilder::new_for_test()
5195 .with_random_signing_identity("alice", TEST_CIPHER_SUITE)
5196 .await
5197 .custom_proposal_type(ProposalType::SELF_REMOVE)
5198 .build()
5199 .create_group(ExtensionList::new(), Default::default(), None)
5200 .await
5201 .unwrap();
5202
5203 alice.propose_self_remove(Vec::new()).await.unwrap();
5204 let again = alice.propose_self_remove(Vec::new()).await;
5205 assert_matches!(again, Err(MlsError::SelfRemoveAlreadyProposed));
5206 }
5207
5208 #[cfg(all(
5209 feature = "by_ref_proposal",
5210 feature = "custom_proposal",
5211 feature = "self_remove_proposal"
5212 ))]
5213 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5214 async fn client_can_self_remove_and_another_client_can_commit() {
5215 let (mut alice, mut bob) = self_remove_group_setup().await;
5216
5217 let carol_client = TestClientBuilder::new_for_test()
5218 .with_random_signing_identity("carol", TEST_CIPHER_SUITE)
5219 .await
5220 .custom_proposal_type(ProposalType::SELF_REMOVE)
5221 .build();
5222
5223 let commit = alice
5225 .commit_builder()
5226 .add_member(
5227 carol_client
5228 .generate_key_package_message(Default::default(), Default::default(), None)
5229 .await
5230 .unwrap(),
5231 )
5232 .unwrap()
5233 .build()
5234 .await
5235 .unwrap();
5236
5237 alice.apply_pending_commit().await.unwrap();
5238 bob.process_incoming_message(commit.commit_message)
5239 .await
5240 .unwrap();
5241
5242 let mut carol = carol_client
5243 .join_group(None, &commit.welcome_messages[0], None)
5244 .await
5245 .unwrap()
5246 .0;
5247
5248 let bob_self_remove = bob.propose_self_remove(Vec::new()).await.unwrap();
5250
5251 alice
5256 .process_incoming_message(bob_self_remove.clone())
5257 .await
5258 .unwrap();
5259 carol
5260 .process_incoming_message(bob_self_remove)
5261 .await
5262 .unwrap();
5263
5264 let commit = alice.commit(Vec::new()).await.unwrap();
5266 alice.apply_pending_commit().await.unwrap();
5267
5268 let expected_members = vec![
5270 alice.member_at_index(alice.current_member_index()).unwrap(),
5271 carol.member_at_index(carol.current_member_index()).unwrap(),
5272 ];
5273 itertools::assert_equal(alice.roster().members_iter(), expected_members.clone());
5274
5275 carol
5277 .process_incoming_message(commit.commit_message.clone())
5278 .await
5279 .unwrap();
5280 itertools::assert_equal(carol.roster().members_iter(), expected_members.clone());
5281 bob.process_incoming_message(commit.commit_message)
5283 .await
5284 .unwrap();
5285 }
5286
5287 #[cfg(all(
5288 feature = "by_ref_proposal",
5289 feature = "custom_proposal",
5290 feature = "self_remove_proposal"
5291 ))]
5292 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5293 async fn commit_with_both_remove_and_self_remove_for_same_client_leaves_remove_unused() {
5294 let (mut alice, mut bob) = self_remove_group_setup().await;
5295
5296 let bob_self_remove = bob.propose_self_remove(Vec::new()).await.unwrap();
5298
5299 alice
5301 .process_incoming_message(bob_self_remove.clone())
5302 .await
5303 .unwrap();
5304
5305 alice.propose_remove(1, Vec::new()).await.unwrap();
5307
5308 let commit = alice.commit(Vec::new()).await.unwrap();
5310 let unused = &commit.unused_proposals[0];
5311
5312 let expected_index = LeafIndex::unchecked(1);
5313
5314 assert_matches!(
5315 unused,
5316 ProposalInfo {
5317 proposal: Proposal::Remove(RemoveProposal {
5318 to_remove: i,
5319 }),
5320 sender: Sender::Member(0),
5321 ..
5322 }
5323 if *i == expected_index);
5324 }
5325
5326 #[cfg(all(
5327 feature = "by_ref_proposal",
5328 feature = "custom_proposal",
5329 feature = "self_remove_proposal"
5330 ))]
5331 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5332 async fn client_processing_commit_with_self_remove_without_processing_proposal_first_errors() {
5333 let (mut alice, mut bob) = self_remove_group_setup().await;
5334
5335 let carol_client = TestClientBuilder::new_for_test()
5336 .with_random_signing_identity("carol", TEST_CIPHER_SUITE)
5337 .await
5338 .custom_proposal_type(ProposalType::SELF_REMOVE)
5339 .build();
5340
5341 let commit = alice
5343 .commit_builder()
5344 .add_member(
5345 carol_client
5346 .generate_key_package_message(Default::default(), Default::default(), None)
5347 .await
5348 .unwrap(),
5349 )
5350 .unwrap()
5351 .build()
5352 .await
5353 .unwrap();
5354 let mut carol = carol_client
5355 .join_group(None, &commit.welcome_messages[0], None)
5356 .await
5357 .unwrap()
5358 .0;
5359 alice
5360 .process_incoming_message(commit.commit_message.clone())
5361 .await
5362 .unwrap();
5363 bob.process_incoming_message(commit.commit_message)
5364 .await
5365 .unwrap();
5366
5367 let bob_self_remove = bob.propose_self_remove(Vec::new()).await.unwrap();
5369
5370 alice
5372 .process_incoming_message(bob_self_remove.clone())
5373 .await
5374 .unwrap();
5375
5376 let remove_bob_commit = alice.commit(Vec::new()).await.unwrap();
5377
5378 let carol_attempts_commit_processing = carol
5381 .process_incoming_message(remove_bob_commit.commit_message)
5382 .await;
5383 assert_matches!(
5384 carol_attempts_commit_processing,
5385 Err(MlsError::ProposalNotFound)
5386 );
5387 }
5388
5389 #[cfg(all(
5390 feature = "by_ref_proposal",
5391 feature = "custom_proposal",
5392 feature = "self_remove_proposal"
5393 ))]
5394 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5395 async fn external_commit_can_have_self_remove() {
5396 let (mut alice, mut bob) = self_remove_group_setup().await;
5397
5398 let carol_client = TestClientBuilder::new_for_test()
5399 .with_random_signing_identity("carol", TEST_CIPHER_SUITE)
5400 .await
5401 .custom_proposal_type(ProposalType::SELF_REMOVE)
5402 .build();
5403
5404 let commit = alice
5406 .commit_builder()
5407 .add_member(
5408 carol_client
5409 .generate_key_package_message(Default::default(), Default::default(), None)
5410 .await
5411 .unwrap(),
5412 )
5413 .unwrap()
5414 .build()
5415 .await
5416 .unwrap();
5417 let mut carol = carol_client
5418 .join_group(None, &commit.welcome_messages[0], None)
5419 .await
5420 .unwrap()
5421 .0;
5422 alice
5423 .process_incoming_message(commit.commit_message.clone())
5424 .await
5425 .unwrap();
5426 bob.process_incoming_message(commit.commit_message)
5427 .await
5428 .unwrap();
5429
5430 let bob_self_remove = bob.propose_self_remove(Vec::new()).await.unwrap();
5431
5432 let group_info = alice
5433 .group_info_message_allowing_ext_commit(true)
5434 .await
5435 .unwrap();
5436 carol
5437 .process_incoming_message(bob_self_remove.clone())
5438 .await
5439 .unwrap();
5440 alice
5441 .process_incoming_message(bob_self_remove.clone())
5442 .await
5443 .unwrap();
5444
5445 let (mut carol_new_group, commit) = carol_client
5446 .external_commit_builder()
5447 .unwrap()
5448 .with_removal(carol.current_member_index())
5449 .with_received_custom_proposal(bob_self_remove)
5450 .build(group_info)
5451 .await
5452 .unwrap();
5453 bob.process_incoming_message(commit.clone()).await.unwrap();
5454 alice
5455 .process_incoming_message(commit.clone())
5456 .await
5457 .unwrap();
5458
5459 let encrypted_message = alice
5461 .encrypt_application_message(b"test", vec![])
5462 .await
5463 .unwrap();
5464 carol_new_group
5465 .process_incoming_message(encrypted_message)
5466 .await
5467 .unwrap();
5468
5469 let alice_identity = alice.current_member_signing_identity().unwrap();
5471 let carol_identity = carol_new_group.current_member_signing_identity().unwrap();
5472 let expected_member_identities = vec![alice_identity.clone(), carol_identity.clone()];
5473 itertools::assert_equal(
5474 alice.roster().members_iter().map(|m| m.signing_identity),
5475 expected_member_identities.clone(),
5476 );
5477 itertools::assert_equal(
5478 carol_new_group
5479 .roster()
5480 .members_iter()
5481 .map(|m| m.signing_identity),
5482 expected_member_identities.clone(),
5483 );
5484
5485 let carol_old_identity = carol_new_group.current_member_signing_identity().unwrap();
5487 assert!(carol_identity == carol_old_identity);
5488 }
5489
5490 #[cfg(all(
5491 feature = "by_ref_proposal",
5492 feature = "custom_proposal",
5493 feature = "self_remove_proposal"
5494 ))]
5495 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5496 async fn can_build_external_commit_from_group_with_self_remove() {
5497 let (mut alice, mut bob) = self_remove_group_setup().await;
5498
5499 let carol_client = TestClientBuilder::new_for_test()
5500 .with_random_signing_identity("carol", TEST_CIPHER_SUITE)
5501 .await
5502 .custom_proposal_type(ProposalType::SELF_REMOVE)
5503 .build();
5504
5505 let commit = alice
5507 .commit_builder()
5508 .add_member(
5509 carol_client
5510 .generate_key_package_message(Default::default(), Default::default(), None)
5511 .await
5512 .unwrap(),
5513 )
5514 .unwrap()
5515 .build()
5516 .await
5517 .unwrap();
5518 let carol = carol_client
5519 .join_group(None, &commit.welcome_messages[0], None)
5520 .await
5521 .unwrap()
5522 .0;
5523 alice
5524 .process_incoming_message(commit.commit_message.clone())
5525 .await
5526 .unwrap();
5527 bob.process_incoming_message(commit.commit_message)
5528 .await
5529 .unwrap();
5530
5531 let bob_self_remove = bob.propose_self_remove(Vec::new()).await.unwrap();
5532 alice
5533 .process_incoming_message(bob_self_remove.clone())
5534 .await
5535 .unwrap();
5536
5537 let remove_bob_commit = alice.commit(Vec::new()).await.unwrap();
5539
5540 alice
5541 .process_incoming_message(remove_bob_commit.commit_message.clone())
5542 .await
5543 .unwrap();
5544
5545 let group_info = alice
5546 .group_info_message_allowing_ext_commit(true)
5547 .await
5548 .unwrap();
5549
5550 let (_, commit) = carol_client
5552 .external_commit_builder()
5553 .unwrap()
5554 .with_removal(carol.current_member_index())
5555 .build(group_info)
5556 .await
5557 .unwrap();
5558 alice
5559 .process_incoming_message(commit.clone())
5560 .await
5561 .unwrap();
5562 }
5563
5564 #[cfg(all(
5565 feature = "by_ref_proposal",
5566 feature = "custom_proposal",
5567 feature = "self_remove_proposal"
5568 ))]
5569 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5570 async fn external_commit_can_have_multiple_self_removes() {
5571 let (mut alice, mut bob) = self_remove_group_setup().await;
5572
5573 let carol_client = TestClientBuilder::new_for_test()
5574 .with_random_signing_identity("carol", TEST_CIPHER_SUITE)
5575 .await
5576 .custom_proposal_type(ProposalType::SELF_REMOVE)
5577 .build();
5578
5579 let commit = alice
5581 .commit_builder()
5582 .add_member(
5583 carol_client
5584 .generate_key_package_message(Default::default(), Default::default(), None)
5585 .await
5586 .unwrap(),
5587 )
5588 .unwrap()
5589 .build()
5590 .await
5591 .unwrap();
5592 let mut carol = carol_client
5593 .join_group(None, &commit.welcome_messages[0], None)
5594 .await
5595 .unwrap()
5596 .0;
5597 alice
5598 .process_incoming_message(commit.commit_message.clone())
5599 .await
5600 .unwrap();
5601 bob.process_incoming_message(commit.commit_message)
5602 .await
5603 .unwrap();
5604
5605 let bob_self_remove = bob.propose_self_remove(Vec::new()).await.unwrap();
5606 let alice_self_remove = alice.propose_self_remove(Vec::new()).await.unwrap();
5607
5608 let group_info = alice
5609 .group_info_message_allowing_ext_commit(true)
5610 .await
5611 .unwrap();
5612 carol
5613 .process_incoming_message(bob_self_remove.clone())
5614 .await
5615 .unwrap();
5616 alice
5617 .process_incoming_message(bob_self_remove.clone())
5618 .await
5619 .unwrap();
5620 bob.process_incoming_message(alice_self_remove.clone())
5621 .await
5622 .unwrap();
5623 carol
5624 .process_incoming_message(alice_self_remove.clone())
5625 .await
5626 .unwrap();
5627
5628 let (carol_new_group, commit) = carol_client
5629 .external_commit_builder()
5630 .unwrap()
5631 .with_removal(carol.current_member_index())
5632 .with_received_custom_proposal(bob_self_remove)
5633 .with_received_custom_proposal(alice_self_remove)
5634 .build(group_info)
5635 .await
5636 .unwrap();
5637
5638 carol
5639 .process_incoming_message(commit.clone())
5640 .await
5641 .unwrap();
5642 bob.process_incoming_message(commit.clone()).await.unwrap();
5643 alice.process_incoming_message(commit).await.unwrap();
5644
5645 let carol_identity = carol_new_group.current_member_signing_identity().unwrap();
5647 let expected_member_identities = vec![carol_identity.clone()];
5648 itertools::assert_equal(
5649 carol_new_group
5650 .roster()
5651 .members_iter()
5652 .map(|m| m.signing_identity),
5653 expected_member_identities.clone(),
5654 );
5655 }
5656
5657 #[cfg(feature = "std")]
5658 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5659 async fn commit_processes_with_custom_time() {
5660 let mut current_time: u64 = 1740000000;
5662
5663 let (bob_identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;
5664 let bob = TestClientBuilder::new_for_test()
5665 .signing_identity(bob_identity, secret_key, TEST_CIPHER_SUITE)
5666 .build();
5667
5668 current_time += 10;
5669
5670 let (alice_identity, secret_key) =
5671 get_test_signing_identity(TEST_CIPHER_SUITE, b"alice").await;
5672 let alice = TestClientBuilder::new_for_test()
5673 .signing_identity(alice_identity, secret_key, TEST_CIPHER_SUITE)
5674 .build();
5675
5676 current_time += 10;
5677
5678 let mut alice_group = alice
5679 .create_group(
5680 Default::default(),
5681 Default::default(),
5682 Some(current_time.into()),
5683 )
5684 .await
5685 .unwrap();
5686
5687 current_time += 10;
5688
5689 let bob_key_package = bob
5690 .generate_key_package_message(
5691 Default::default(),
5692 Default::default(),
5693 Some(current_time.into()),
5694 )
5695 .await
5696 .unwrap();
5697
5698 current_time += 10;
5699
5700 let commit_output = alice_group
5701 .commit_builder()
5702 .add_member(bob_key_package)
5703 .unwrap()
5704 .commit_time(current_time.into())
5705 .build()
5706 .await
5707 .unwrap();
5708
5709 current_time += 10;
5710
5711 alice_group
5712 .process_incoming_message_with_time(commit_output.commit_message, current_time.into())
5713 .await
5714 .unwrap();
5715 }
5716
5717 #[cfg(feature = "custom_proposal")]
5718 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5719 async fn custom_proposal_may_enforce_path() {
5720 test_custom_proposal_mls_rules(true).await;
5721 }
5722
5723 #[cfg(feature = "custom_proposal")]
5724 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5725 async fn custom_proposal_need_not_enforce_path() {
5726 test_custom_proposal_mls_rules(false).await;
5727 }
5728
5729 #[cfg(feature = "custom_proposal")]
5730 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
5731 async fn test_custom_proposal_mls_rules(path_required_for_custom: bool) {
5732 let mls_rules = CustomMlsRules {
5733 path_required_for_custom,
5734 external_joiner_can_send_custom: true,
5735 };
5736
5737 let mut alice = client_with_custom_rules(b"alice", mls_rules.clone())
5738 .await
5739 .create_group(Default::default(), Default::default(), None)
5740 .await
5741 .unwrap();
5742
5743 let alice_pub_before = alice.current_user_leaf_node().unwrap().public_key.clone();
5744
5745 let kp = client_with_custom_rules(b"bob", mls_rules)
5746 .await
5747 .generate_key_package_message(Default::default(), Default::default(), None)
5748 .await
5749 .unwrap();
5750
5751 alice
5752 .commit_builder()
5753 .custom_proposal(CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![]))
5754 .add_member(kp)
5755 .unwrap()
5756 .build()
5757 .await
5758 .unwrap();
5759
5760 alice.apply_pending_commit().await.unwrap();
5761
5762 let alice_pub_after = &alice.current_user_leaf_node().unwrap().public_key;
5763
5764 if path_required_for_custom {
5765 assert_ne!(alice_pub_after, &alice_pub_before);
5766 } else {
5767 assert_eq!(alice_pub_after, &alice_pub_before);
5768 }
5769 }
5770
5771 #[cfg(feature = "custom_proposal")]
5772 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5773 async fn custom_proposal_by_value_in_external_join_may_be_allowed() {
5774 test_custom_proposal_by_value_in_external_join(true).await
5775 }
5776
5777 #[cfg(feature = "custom_proposal")]
5778 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5779 async fn custom_proposal_by_value_in_external_join_may_not_be_allowed() {
5780 test_custom_proposal_by_value_in_external_join(false).await
5781 }
5782
5783 #[cfg(feature = "custom_proposal")]
5784 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
5785 async fn test_custom_proposal_by_value_in_external_join(external_joiner_can_send_custom: bool) {
5786 let mls_rules = CustomMlsRules {
5787 path_required_for_custom: true,
5788 external_joiner_can_send_custom,
5789 };
5790
5791 let mut alice = client_with_custom_rules(b"alice", mls_rules.clone())
5792 .await
5793 .create_group(Default::default(), Default::default(), None)
5794 .await
5795 .unwrap();
5796
5797 let group_info = alice
5798 .group_info_message_allowing_ext_commit(true)
5799 .await
5800 .unwrap();
5801
5802 let commit = client_with_custom_rules(b"bob", mls_rules)
5803 .await
5804 .external_commit_builder()
5805 .unwrap()
5806 .with_custom_proposal(CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![]))
5807 .build(group_info)
5808 .await;
5809
5810 if external_joiner_can_send_custom {
5811 let commit = commit.unwrap().1;
5812 alice.process_incoming_message(commit).await.unwrap();
5813 } else {
5814 assert_matches!(commit.map(|_| ()), Err(MlsError::MlsRulesError(_)));
5815 }
5816 }
5817
5818 #[cfg(feature = "custom_proposal")]
5819 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5820 async fn custom_proposal_by_ref_in_external_join() {
5821 let mls_rules = CustomMlsRules {
5822 path_required_for_custom: true,
5823 external_joiner_can_send_custom: true,
5824 };
5825
5826 let mut alice = client_with_custom_rules(b"alice", mls_rules.clone())
5827 .await
5828 .create_group(Default::default(), Default::default(), None)
5829 .await
5830 .unwrap();
5831
5832 let by_ref = CustomProposal::new(TEST_CUSTOM_PROPOSAL_TYPE, vec![]);
5833 let by_ref = alice.propose_custom(by_ref, vec![]).await.unwrap();
5834
5835 let group_info = alice
5836 .group_info_message_allowing_ext_commit(true)
5837 .await
5838 .unwrap();
5839
5840 let (_, commit) = client_with_custom_rules(b"bob", mls_rules)
5841 .await
5842 .external_commit_builder()
5843 .unwrap()
5844 .with_received_custom_proposal(by_ref)
5845 .build(group_info)
5846 .await
5847 .unwrap();
5848
5849 alice.process_incoming_message(commit).await.unwrap();
5850 }
5851
5852 #[cfg(feature = "custom_proposal")]
5853 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
5854 async fn client_with_custom_rules(
5855 name: &[u8],
5856 mls_rules: CustomMlsRules,
5857 ) -> Client<impl MlsConfig> {
5858 let (signing_identity, signer) = get_test_signing_identity(TEST_CIPHER_SUITE, name).await;
5859
5860 ClientBuilder::new()
5861 .crypto_provider(TestCryptoProvider::new())
5862 .identity_provider(BasicWithCustomProvider::new(BasicIdentityProvider::new()))
5863 .signing_identity(signing_identity, signer, TEST_CIPHER_SUITE)
5864 .custom_proposal_type(TEST_CUSTOM_PROPOSAL_TYPE)
5865 .mls_rules(mls_rules)
5866 .build()
5867 }
5868
5869 #[derive(Debug, Clone)]
5870 struct CustomMlsRules {
5871 path_required_for_custom: bool,
5872 external_joiner_can_send_custom: bool,
5873 }
5874
5875 #[cfg(feature = "custom_proposal")]
5876 impl ProposalBundle {
5877 fn has_test_custom_proposal(&self) -> bool {
5878 self.custom_proposal_types()
5879 .any(|t| t == TEST_CUSTOM_PROPOSAL_TYPE)
5880 }
5881 }
5882
5883 #[cfg(feature = "custom_proposal")]
5884 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
5885 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
5886 impl crate::MlsRules for CustomMlsRules {
5887 type Error = MlsError;
5888
5889 fn commit_options(
5890 &self,
5891 _: &Roster,
5892 _: &GroupContext,
5893 proposals: &ProposalBundle,
5894 ) -> Result<CommitOptions, MlsError> {
5895 Ok(CommitOptions::default().with_path_required(
5896 !proposals.has_test_custom_proposal() || self.path_required_for_custom,
5897 ))
5898 }
5899
5900 fn encryption_options(
5901 &self,
5902 _: &Roster,
5903 _: &GroupContext,
5904 ) -> Result<crate::mls_rules::EncryptionOptions, MlsError> {
5905 Ok(Default::default())
5906 }
5907
5908 async fn filter_proposals(
5909 &self,
5910 _: CommitDirection,
5911 sender: CommitSource,
5912 _: &Roster,
5913 _: &GroupContext,
5914 proposals: ProposalBundle,
5915 ) -> Result<ProposalBundle, MlsError> {
5916 let is_external = matches!(sender, CommitSource::NewMember(_));
5917 let has_custom = proposals.has_test_custom_proposal();
5918 let allowed = !has_custom || !is_external || self.external_joiner_can_send_custom;
5919
5920 allowed.then_some(proposals).ok_or(MlsError::InvalidSender)
5921 }
5922 }
5923
5924 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5925 async fn group_can_receive_commit_from_self() {
5926 let mut group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
5927
5928 let commit = group.commit(vec![]).await.unwrap();
5929
5930 let update = group
5931 .process_incoming_message(commit.commit_message)
5932 .await
5933 .unwrap();
5934
5935 let ReceivedMessage::Commit(update) = update else {
5936 panic!("expected commit message")
5937 };
5938
5939 assert_eq!(update.committer, *group.private_tree.self_index);
5940 }
5941
5942 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5943 async fn can_process_commit_when_pending_commit() {
5944 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 2).await;
5945
5946 let commit = groups[0].commit(vec![]).await.unwrap().commit_message;
5947 groups[1].commit(vec![]).await.unwrap();
5948
5949 groups[1].process_incoming_message(commit).await.unwrap();
5950
5951 let res = groups[1].apply_pending_commit().await;
5952 assert_matches!(res, Err(MlsError::PendingCommitNotFound));
5953 }
5954
5955 #[cfg(feature = "by_ref_proposal")]
5956 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5957 async fn can_process_own_plaintext_proposal() {
5958 can_process_own_proposal(false).await;
5959 }
5960
5961 #[cfg(feature = "by_ref_proposal")]
5962 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
5963 async fn can_process_own_ciphertext_proposal() {
5964 can_process_own_proposal(true).await;
5965 }
5966
5967 #[cfg(feature = "by_ref_proposal")]
5968 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
5969 async fn can_process_own_proposal(encrypt_proposal: bool) {
5970 let (alice, _) = test_client_with_key_pkg_custom(
5971 TEST_PROTOCOL_VERSION,
5972 TEST_CIPHER_SUITE,
5973 "alice",
5974 Default::default(),
5975 Default::default(),
5976 |c| c.0.mls_rules.encryption_options.encrypt_control_messages = encrypt_proposal,
5977 )
5978 .await;
5979
5980 let mut alice = TestGroup {
5981 group: alice
5982 .create_group(Default::default(), Default::default(), None)
5983 .await
5984 .unwrap(),
5985 };
5986
5987 let mut bob = alice.join("bob").await.0;
5988 let mut alice = alice;
5989
5990 let upd = alice.propose_update(vec![]).await.unwrap();
5991 alice.process_incoming_message(upd.clone()).await.unwrap();
5992
5993 bob.process_incoming_message(upd).await.unwrap();
5994 let commit = bob.commit(vec![]).await.unwrap().commit_message;
5995 let update = alice.process_incoming_message(commit).await.unwrap();
5996
5997 let ReceivedMessage::Commit(CommitMessageDescription {
5998 effect: CommitEffect::NewEpoch(new_epoch),
5999 ..
6000 }) = update
6001 else {
6002 panic!("unexpected commit effect")
6003 };
6004
6005 assert_eq!(new_epoch.applied_proposals.len(), 1);
6006 assert_eq!(new_epoch.applied_proposals[0].sender, Sender::Member(0));
6007 }
6008
6009 #[cfg(feature = "by_ref_proposal")]
6010 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
6011 async fn commit_clears_proposals() {
6012 let mut groups = test_n_member_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, 2).await;
6013
6014 groups[0].propose_update(vec![]).await.unwrap();
6015
6016 assert_eq!(groups[0].state.proposals.proposals.len(), 1);
6017 assert_eq!(groups[0].state.proposals.own_proposals.len(), 1);
6018
6019 let commit = groups[1].commit(vec![]).await.unwrap().commit_message;
6020 groups[0].process_message(commit).await.unwrap();
6021
6022 assert!(groups[0].state.proposals.proposals.is_empty());
6023 assert!(groups[0].state.proposals.own_proposals.is_empty());
6024 }
6025
6026 #[cfg(feature = "by_ref_proposal")]
6027 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
6028 async fn commit_required_is_true_when_proposals_pending() {
6029 let mut group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
6030
6031 assert!(!group.commit_required());
6032
6033 group
6034 .propose_group_context_extensions(ExtensionList::new(), vec![])
6035 .await
6036 .unwrap();
6037
6038 assert!(group.commit_required());
6039
6040 let res = group.encrypt_application_message(&[0u8; 32], vec![]).await;
6041
6042 assert_matches!(res, Err(MlsError::CommitRequired));
6043
6044 group.commit(vec![]).await.unwrap();
6045 group.apply_pending_commit().await.unwrap();
6046
6047 assert!(!group.commit_required());
6048 }
6049
6050 #[cfg(feature = "std")]
6052 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
6053 async fn can_be_stored_without_tree() {
6054 let mut group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
6055 let storage = group.config.group_state_storage().inner;
6056
6057 group.write_to_storage().await.unwrap();
6058 let snapshot_with_tree = storage.lock().unwrap().drain().next().unwrap().1;
6059
6060 group.write_to_storage_without_ratchet_tree().await.unwrap();
6061 let snapshot_without_tree = storage.lock().unwrap().iter().next().unwrap().1.clone();
6062
6063 let tree = group.state.public_tree.nodes.mls_encode_to_vec().unwrap();
6064 let empty_tree = Vec::<u8>::new().mls_encode_to_vec().unwrap();
6065
6066 assert_eq!(
6067 snapshot_with_tree.state_data.len() - snapshot_without_tree.state_data.len(),
6068 tree.len() - empty_tree.len()
6069 );
6070
6071 let exported_tree = group.export_tree();
6072
6073 let restored = Client::new(group.config.clone(), None, None, TEST_PROTOCOL_VERSION)
6074 .load_group_with_ratchet_tree(group.group_id(), exported_tree)
6075 .await
6076 .unwrap();
6077
6078 assert_eq!(restored.group_state(), group.group_state());
6079 }
6080
6081 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
6082 async fn delete_exporter() {
6083 let mut group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
6084
6085 group.export_secret(b"123", b"", 15).await.unwrap();
6086
6087 group.delete_exporter();
6088 let res = group.export_secret(b"123", b"", 15).await;
6089 assert_matches!(res, Err(MlsError::ExporterDeleted));
6090
6091 group.commit(vec![]).await.unwrap();
6092 group.apply_pending_commit().await.unwrap();
6093 group.export_secret(b"123", b"", 15).await.unwrap();
6094 }
6095}