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