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