1use crate::error::{MlsError, Result};
4use crate::group::{GroupManager, DEFAULT_CIPHERSUITE};
5use crate::provider::MlsProvider;
6use crate::session::SessionManager;
7use crate::storage::MlsStorage;
8use crate::storage_adapter::MlsStorageAdapter;
9use crate::types::{
10 EncryptedMessage, GroupId, GroupInfo, GroupMetadata, GroupRole, KeyPackageBundle,
11 MlsMessageType, StorageKeyType, WelcomeMessage,
12};
13
14use offline_protocol_core::Address;
15use openmls::prelude::tls_codec::{Deserialize as TlsDeserialize, Serialize as TlsSerialize};
16use openmls::prelude::*;
17use openmls_basic_credential::SignatureKeyPair;
18use openmls_traits::OpenMlsProvider;
19use std::sync::{Arc, RwLock};
20use tracing::{debug, info, warn};
21use uuid::Uuid;
22
23const ED25519_PUBLIC_KEY_LEN: usize = 32;
25
26const DEFAULT_KEY_PACKAGE_LIFETIME_SECS: u64 = 30 * 24 * 60 * 60;
28
29const KEY_PACKAGE_PURGE_GRACE_SECS: u64 = 7 * 24 * 60 * 60;
39
40pub const MAX_PUSH_KEY_PACKAGES: usize = 64;
51
52#[derive(Debug, Clone)]
59pub struct PushKeyPackage {
60 pub bundle: KeyPackageBundle,
62 pub pool_exhausted: bool,
64}
65
66fn newer_than(candidate: &KeyPackageBundle, current: Option<&KeyPackageBundle>) -> bool {
69 current.is_none_or(|existing| candidate.created_at_ms > existing.created_at_ms)
70}
71
72pub struct MlsManager {
74 user_id: String,
76
77 storage: Arc<dyn MlsStorage>,
79
80 provider: MlsProvider,
82
83 credential: RwLock<Option<CredentialWithKey>>,
85
86 cached_signer: RwLock<Option<SignatureKeyPair>>,
88
89 session_manager: SessionManager,
91
92 group_manager: GroupManager,
94}
95
96impl MlsManager {
97 pub fn new(user_id: impl Into<String>, storage: Arc<dyn MlsStorage>) -> Result<Self> {
99 let user_id = user_id.into();
100
101 let adapter = MlsStorageAdapter::new(storage.clone());
102 let provider = MlsProvider::new(adapter);
103
104 let session_manager =
105 SessionManager::new(user_id.clone(), storage.clone(), provider.clone());
106 let group_manager = GroupManager::new(storage.clone(), provider.clone());
107
108 let manager = Self {
109 user_id,
110 storage,
111 provider,
112 credential: RwLock::new(None),
113 cached_signer: RwLock::new(None),
114 session_manager,
115 group_manager,
116 };
117
118 manager.ensure_identity()?;
119
120 Ok(manager)
121 }
122
123 pub fn user_id(&self) -> &str {
125 &self.user_id
126 }
127
128 pub fn load_or_create_identity(
144 storage: &Arc<dyn MlsStorage>,
145 ) -> Result<(SignatureKeyPair, Address)> {
146 let key_type = StorageKeyType::Identity.as_str();
147
148 let signature_keys = match storage.load(key_type, "key_pair")? {
149 Some(json) => serde_json::from_slice(&json).map_err(|e| {
150 MlsError::Deserialization(format!("Failed to deserialize signature keys: {}", e))
151 })?,
152 None => {
153 let keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
154 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
155 let keys_json = serde_json::to_vec(&keys)
156 .map_err(|e| MlsError::Serialization(e.to_string()))?;
157 storage.store(key_type, "key_pair", &keys_json)?;
158 info!("Minted a new identity key");
159 keys
160 }
161 };
162
163 let address = Self::derive_address(signature_keys.public())?;
164 Ok((signature_keys, address))
165 }
166
167 fn ensure_identity(&self) -> Result<()> {
169 if self.load_identity()? {
170 return Ok(());
171 }
172 self.create_identity()?;
173 info!(user_id = %self.user_id, "Created new MLS identity");
174 Ok(())
175 }
176
177 fn verify_identity_binding(&self, public_key: &[u8]) -> Result<()> {
184 let Ok(expected) = self.user_id.parse::<Address>() else {
185 return Ok(());
186 };
187
188 let derived = Self::derive_address(public_key)?;
189 if derived != expected {
190 return Err(MlsError::IdentityAddressMismatch {
191 expected: expected.to_string(),
192 derived: derived.to_string(),
193 });
194 }
195 Ok(())
196 }
197
198 fn load_identity(&self) -> Result<bool> {
200 let key_type = StorageKeyType::Identity.as_str();
201 let keys_data = self.storage.load(key_type, "key_pair")?;
202
203 match keys_data {
204 Some(json) => {
205 let signature_keys: SignatureKeyPair =
206 serde_json::from_slice(&json).map_err(|e| {
207 MlsError::Deserialization(format!(
208 "Failed to deserialize signature keys: {}",
209 e
210 ))
211 })?;
212
213 let public_key = signature_keys.public();
214
215 self.verify_identity_binding(public_key)?;
220
221 let credential =
222 Credential::new(CredentialType::Basic, self.user_id.as_bytes().to_vec());
223
224 let credential_with_key = CredentialWithKey {
225 credential,
226 signature_key: public_key.into(),
227 };
228
229 {
230 let mut guard = self
231 .credential
232 .write()
233 .map_err(|_| MlsError::NotInitialized)?;
234 *guard = Some(credential_with_key);
235 }
236 {
237 let mut guard = self
238 .cached_signer
239 .write()
240 .map_err(|_| MlsError::NotInitialized)?;
241 *guard = Some(signature_keys);
242 }
243
244 debug!(user_id = %self.user_id, "Loaded existing MLS identity");
245 Ok(true)
246 }
247 None => Ok(false),
248 }
249 }
250
251 fn create_identity(&self) -> Result<()> {
253 let signature_keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
254 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
255
256 self.verify_identity_binding(signature_keys.public())?;
264
265 let keys_json = serde_json::to_vec(&signature_keys)
266 .map_err(|e| MlsError::Serialization(e.to_string()))?;
267
268 let key_type = StorageKeyType::Identity.as_str();
269 self.storage.store(key_type, "key_pair", &keys_json)?;
270
271 let public_key = signature_keys.public();
272
273 let credential = Credential::new(CredentialType::Basic, self.user_id.as_bytes().to_vec());
274
275 let credential_with_key = CredentialWithKey {
276 credential,
277 signature_key: public_key.into(),
278 };
279
280 {
281 let mut guard = self
282 .credential
283 .write()
284 .map_err(|_| MlsError::NotInitialized)?;
285 *guard = Some(credential_with_key);
286 }
287 {
288 let mut guard = self
289 .cached_signer
290 .write()
291 .map_err(|_| MlsError::NotInitialized)?;
292 *guard = Some(signature_keys);
293 }
294
295 Ok(())
296 }
297
298 fn get_credential(&self) -> Result<CredentialWithKey> {
300 let guard = self
301 .credential
302 .read()
303 .map_err(|_| MlsError::NotInitialized)?;
304 guard.clone().ok_or(MlsError::NotInitialized)
305 }
306
307 fn get_signer(&self) -> Result<SignatureKeyPair> {
312 let guard = self
313 .cached_signer
314 .read()
315 .map_err(|_| MlsError::NotInitialized)?;
316
317 let cached = guard.as_ref().ok_or(MlsError::NotInitialized)?;
318 let bytes =
319 serde_json::to_vec(cached).map_err(|e| MlsError::Serialization(e.to_string()))?;
320 serde_json::from_slice(&bytes).map_err(|e| MlsError::Deserialization(e.to_string()))
321 }
322
323 pub fn generate_key_package(&self) -> Result<KeyPackageBundle> {
329 self.generate_key_package_inner(false)
330 }
331
332 pub fn generate_publication_key_package(&self) -> Result<KeyPackageBundle> {
339 self.generate_key_package_inner(true)
340 }
341
342 fn generate_key_package_inner(&self, reserved: bool) -> Result<KeyPackageBundle> {
343 let credential = self.get_credential()?;
344 let signature_keys = self.get_signer()?;
345
346 let key_package_bundle = KeyPackage::builder()
347 .build(
348 DEFAULT_CIPHERSUITE,
349 &self.provider,
350 &signature_keys,
351 credential,
352 )
353 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?;
354
355 let key_package_data = key_package_bundle
356 .key_package()
357 .tls_serialize_detached()
358 .map_err(|e| MlsError::Serialization(e.to_string()))?;
359
360 let provider_hash_ref = key_package_bundle
365 .key_package()
366 .hash_ref(self.provider.crypto())
367 .ok()
368 .and_then(|hash_ref| hash_ref.tls_serialize_detached().ok());
369
370 let package_id = Uuid::new_v4().to_string();
371
372 let key_type = StorageKeyType::KeyPackage.as_str();
373 let mut bundle = KeyPackageBundle::new(
374 package_id,
375 self.user_id.clone(),
376 key_package_data,
377 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
378 );
379 bundle.reserved_for_publication = reserved;
380 bundle.provider_hash_ref = provider_hash_ref;
381 let serialized =
382 serde_json::to_vec(&bundle).map_err(|e| MlsError::Serialization(e.to_string()))?;
383 self.storage
384 .store(key_type, &bundle.package_id, &serialized)?;
385
386 debug!(package_id = %bundle.package_id, "Generated new key package");
387 Ok(bundle)
388 }
389
390 pub fn get_or_create_key_package(&self) -> Result<KeyPackageBundle> {
408 let key_type = StorageKeyType::KeyPackage.as_str();
409 let packages = self.storage.list_keys(key_type)?;
410
411 for package_id in packages {
412 if let Some(bundle) = self.load_stored_key_package(&package_id)? {
413 if bundle.reserved_for_publication || bundle.assigned_peer.is_some() {
414 continue;
415 }
416 return Ok(bundle);
417 }
418 }
419
420 self.generate_key_package()
421 }
422
423 pub fn take_push_key_package(&self, peer_id: &str) -> Result<PushKeyPackage> {
471 let key_type = StorageKeyType::KeyPackage.as_str();
472 let package_ids = self.storage.list_keys(key_type)?;
473
474 let mut live = 0usize;
475 let mut assigned_here: Option<KeyPackageBundle> = None;
476 let mut unclaimed: Option<KeyPackageBundle> = None;
477 let mut newest: Option<KeyPackageBundle> = None;
478
479 for package_id in package_ids {
483 let Some(bundle) = self.load_stored_key_package(&package_id)? else {
484 continue;
485 };
486 if bundle.reserved_for_publication {
487 continue;
488 }
489 live += 1;
490
491 if bundle.assigned_peer.as_deref() == Some(peer_id) {
492 if newer_than(&bundle, assigned_here.as_ref()) {
494 assigned_here = Some(bundle.clone());
495 }
496 } else if bundle.assigned_peer.is_none() && newer_than(&bundle, unclaimed.as_ref()) {
497 unclaimed = Some(bundle.clone());
498 }
499
500 if newer_than(&bundle, newest.as_ref()) {
501 newest = Some(bundle);
502 }
503 }
504
505 if let Some(bundle) = assigned_here {
506 return Ok(PushKeyPackage {
507 bundle,
508 pool_exhausted: false,
509 });
510 }
511
512 if let Some(bundle) = unclaimed {
515 return Ok(PushKeyPackage {
516 bundle: self.claim_key_package(bundle, peer_id)?,
517 pool_exhausted: false,
518 });
519 }
520
521 if live >= MAX_PUSH_KEY_PACKAGES {
524 if let Some(bundle) = newest {
527 debug!(
528 peer_id = %peer_id,
529 live = live,
530 "Push key-package pool at capacity; reusing an existing package"
531 );
532 return Ok(PushKeyPackage {
533 bundle,
534 pool_exhausted: true,
535 });
536 }
537 }
538
539 let bundle = self.claim_key_package(self.generate_key_package()?, peer_id)?;
540
541 Ok(PushKeyPackage {
542 bundle,
543 pool_exhausted: false,
544 })
545 }
546
547 fn claim_key_package(
549 &self,
550 mut bundle: KeyPackageBundle,
551 peer_id: &str,
552 ) -> Result<KeyPackageBundle> {
553 bundle.assigned_peer = Some(peer_id.to_string());
554 let serialized =
555 serde_json::to_vec(&bundle).map_err(|e| MlsError::Serialization(e.to_string()))?;
556 self.storage.store(
557 StorageKeyType::KeyPackage.as_str(),
558 &bundle.package_id,
559 &serialized,
560 )?;
561 debug!(
562 package_id = %bundle.package_id,
563 peer_id = %peer_id,
564 "Assigned key package to peer"
565 );
566 Ok(bundle)
567 }
568
569 pub fn key_package_by_id(&self, package_id: &str) -> Result<Option<KeyPackageBundle>> {
577 self.load_stored_key_package(package_id)
578 }
579
580 pub fn delete_key_package(&self, package_id: &str) -> Result<()> {
593 let Some(data) = self
594 .storage
595 .load(StorageKeyType::KeyPackage.as_str(), package_id)?
596 else {
597 return Ok(());
598 };
599
600 match serde_json::from_slice::<KeyPackageBundle>(&data) {
601 Ok(bundle) => self.purge_key_package_material(package_id, &bundle),
602 Err(_) => {
610 let legacy = KeyPackageBundle::new(
611 package_id.to_string(),
612 self.user_id.clone(),
613 data,
614 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
615 );
616 self.purge_key_package_material(package_id, &legacy)
617 }
618 }
619 }
620
621 pub fn import_key_package(&self, user_id: &str, key_package_data: &[u8]) -> Result<()> {
636 offline_protocol_core::validate_id_chars(user_id, "User ID")
637 .map_err(|e| MlsError::InvalidUserId(e.to_string()))?;
638
639 let key_package_in = KeyPackageIn::tls_deserialize_exact(key_package_data)
640 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
641 let key_package = key_package_in
642 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
643 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
644
645 Self::verify_credential_identity(&key_package, user_id)?;
646 Self::verify_address_binding(&key_package, user_id)?;
647
648 let key_type = StorageKeyType::ContactKeyPackage.as_str();
649 self.storage.store(key_type, user_id, key_package_data)?;
650
651 debug!(user_id = %user_id, "Imported key package for contact");
652 Ok(())
653 }
654
655 fn get_contact_key_package(&self, user_id: &str) -> Result<KeyPackage> {
657 let key_type = StorageKeyType::ContactKeyPackage.as_str();
658 let data = self
659 .storage
660 .load(key_type, user_id)?
661 .ok_or_else(|| MlsError::NoKeyPackage(user_id.to_string()))?;
662
663 let key_package_in = KeyPackageIn::tls_deserialize_exact(&data)
664 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
665
666 let key_package = key_package_in
668 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
669 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
670
671 Self::verify_credential_identity(&key_package, user_id)?;
677 Self::verify_address_binding(&key_package, user_id)?;
678
679 Ok(key_package)
680 }
681
682 fn verify_address_binding(key_package: &KeyPackage, user_id: &str) -> Result<()> {
711 let claimed = user_id.parse::<Address>().map_err(|e| {
712 MlsError::InvalidUserId(format!("'{}' is not an address: {}", user_id, e))
713 })?;
714
715 let derived = Self::derive_address(key_package.leaf_node().signature_key().as_slice())?;
716 if derived != claimed {
717 return Err(MlsError::KeyPackageAddressMismatch {
718 claimed: claimed.to_string(),
719 derived: derived.to_string(),
720 });
721 }
722 Ok(())
723 }
724
725 fn verify_credential_identity(key_package: &KeyPackage, user_id: &str) -> Result<()> {
729 let identity = key_package.leaf_node().credential().serialized_content();
730 if identity != user_id.as_bytes() {
731 return Err(MlsError::CredentialIdentityMismatch {
732 expected: user_id.to_string(),
733 found: String::from_utf8_lossy(identity).into_owned(),
734 });
735 }
736 Ok(())
737 }
738
739 pub fn get_pending_key_packages(&self) -> Result<Vec<KeyPackageBundle>> {
741 let key_type = StorageKeyType::KeyPackage.as_str();
742 let package_ids = self.storage.list_keys(key_type)?;
743
744 let mut bundles = Vec::new();
745 for package_id in package_ids {
746 if let Some(bundle) = self.load_stored_key_package(&package_id)? {
747 bundles.push(bundle);
748 }
749 }
750
751 Ok(bundles)
752 }
753
754 pub fn mark_key_package_synced(&self, package_id: &str) -> Result<()> {
756 let key_type = StorageKeyType::KeyPackage.as_str();
757 self.storage.delete(key_type, package_id)?;
758 debug!(package_id = %package_id, "Marked key package as synced");
759 Ok(())
760 }
761
762 pub fn has_session(&self, other_user_id: &str) -> Result<bool> {
768 self.session_manager.has_session(other_user_id)
769 }
770
771 pub fn create_session(&self, other_user_id: &str) -> Result<WelcomeMessage> {
773 let their_key_package = self.get_contact_key_package(other_user_id)?;
774 let credential = self.get_credential()?;
775 let signature_keys = self.get_signer()?;
776
777 let welcome = self.session_manager.create_session(
778 other_user_id,
779 their_key_package,
780 &credential,
781 &signature_keys,
782 )?;
783
784 let key_type = StorageKeyType::ContactKeyPackage.as_str();
785 self.storage.delete(key_type, other_user_id)?;
786
787 Ok(welcome)
788 }
789
790 pub fn join_session(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
792 self.session_manager.join_session(welcome)
793 }
794
795 pub fn replace_session_with_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
802 let other_user_id = &welcome.inviter_id;
803
804 offline_protocol_core::validate_id_chars(other_user_id, "User ID")
808 .map_err(|e| MlsError::InvalidUserId(e.to_string()))?;
809
810 self.session_manager.verify_welcome_slot(welcome)?;
815
816 let _ = self.clear_pending_welcome(other_user_id);
818
819 let key_type = StorageKeyType::ContactKeyPackage.as_str();
821 let _ = self.storage.delete(key_type, other_user_id);
822
823 self.session_manager.join_session(welcome)
827 }
828
829 pub fn encrypt_for_user(
831 &self,
832 other_user_id: &str,
833 plaintext: &[u8],
834 ) -> Result<EncryptedMessage> {
835 if !self.has_session(other_user_id)? {
836 let welcome = self.create_session(other_user_id)?;
837 warn!(
838 other_user_id = %other_user_id,
839 "Created new session - Welcome message needs to be sent"
840 );
841 let key_type = StorageKeyType::PendingWelcome.as_str();
842 let welcome_data =
843 serde_json::to_vec(&welcome).map_err(|e| MlsError::Serialization(e.to_string()))?;
844 self.storage.store(key_type, other_user_id, &welcome_data)?;
845 }
846
847 let signature_keys = self.get_signer()?;
848 self.session_manager
849 .encrypt_message(other_user_id, plaintext, &signature_keys)
850 }
851
852 pub fn encrypt_for_existing_session(
859 &self,
860 other_user_id: &str,
861 plaintext: &[u8],
862 ) -> Result<EncryptedMessage> {
863 let signature_keys = self.get_signer()?;
864 self.session_manager
865 .encrypt_message(other_user_id, plaintext, &signature_keys)
866 }
867
868 pub fn get_pending_welcome(&self, other_user_id: &str) -> Result<Option<WelcomeMessage>> {
870 let key_type = StorageKeyType::PendingWelcome.as_str();
871 match self.storage.load(key_type, other_user_id)? {
872 Some(data) => {
873 let welcome: WelcomeMessage = serde_json::from_slice(&data)
874 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
875 Ok(Some(welcome))
876 }
877 None => Ok(None),
878 }
879 }
880
881 pub fn clear_pending_welcome(&self, other_user_id: &str) -> Result<()> {
883 let key_type = StorageKeyType::PendingWelcome.as_str();
884 self.storage.delete(key_type, other_user_id)?;
885 Ok(())
886 }
887
888 pub fn decrypt_from_user(
895 &self,
896 encrypted: &EncryptedMessage,
897 claimed_sender: &str,
898 ) -> Result<Option<Vec<u8>>> {
899 self.session_manager
900 .decrypt_message(encrypted, claimed_sender)
901 }
902
903 pub fn list_sessions(&self) -> Result<Vec<String>> {
905 self.session_manager.list_sessions()
906 }
907
908 pub fn delete_session(&self, other_user_id: &str) -> Result<()> {
910 self.session_manager.delete_session(other_user_id)
911 }
912
913 pub fn create_group(&self, group_name: &str) -> Result<GroupInfo> {
919 let group_id = GroupId::new(format!("group:{}", Uuid::new_v4()))?;
920 let credential = self.get_credential()?;
921 let signature_keys = self.get_signer()?;
922
923 let group = self
924 .group_manager
925 .create_group(&group_id, &credential, &signature_keys)?;
926
927 let metadata = GroupMetadata::new_with_creator(Some(group_name.to_string()), &self.user_id);
929 self.save_group_metadata(&group_id, &metadata)?;
930
931 let mut info = self.group_manager.get_group_info(&group, &group_id);
932 info.name = metadata.name;
933 info.created_at_ms = metadata.created_at_ms;
934 info.last_activity_ms = metadata.last_activity_ms;
935
936 info!(group_id = %group_id, name = %group_name, "Created new group");
937 Ok(info)
938 }
939
940 pub fn add_group_member(
954 &self,
955 group_id: &GroupId,
956 invitee_user_id: &str,
957 member_key_package: &[u8],
958 ) -> Result<(WelcomeMessage, EncryptedMessage)> {
959 let key_package = KeyPackageIn::tls_deserialize_exact(member_key_package)
960 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?
961 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
962 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
963
964 Self::verify_credential_identity(&key_package, invitee_user_id)?;
965 Self::verify_address_binding(&key_package, invitee_user_id)?;
966
967 let mut group = self
968 .group_manager
969 .load_group(group_id)?
970 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
971
972 let signature_keys = self.get_signer()?;
973 let (commit, welcome) =
974 self.group_manager
975 .add_member(&mut group, key_package, &signature_keys)?;
976
977 self.group_manager.save_group(group_id, &group)?;
978
979 let welcome_bytes = welcome
980 .tls_serialize_detached()
981 .map_err(|e| MlsError::Serialization(e.to_string()))?;
982
983 let commit_bytes = commit
984 .tls_serialize_detached()
985 .map_err(|e| MlsError::Serialization(e.to_string()))?;
986
987 let group_name = self.load_group_metadata(group_id)?.and_then(|m| m.name);
989
990 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
991
992 let welcome_msg = WelcomeMessage {
993 group_id: group_id.clone(),
994 welcome_data: welcome_bytes,
995 inviter_id: self.user_id.clone(),
996 group_name,
997 timestamp_ms: now_ms,
998 };
999
1000 let commit_msg = EncryptedMessage {
1001 group_id: group_id.clone(),
1002 message_type: MlsMessageType::Commit,
1003 epoch: group.epoch().as_u64(),
1004 ciphertext: commit_bytes,
1005 sender_id: self.user_id.clone(),
1006 timestamp_ms: now_ms,
1007 };
1008
1009 Ok((welcome_msg, commit_msg))
1010 }
1011
1012 pub fn remove_group_member(
1039 &self,
1040 group_id: &GroupId,
1041 member_id: &str,
1042 ) -> Result<EncryptedMessage> {
1043 let mut group = self
1044 .group_manager
1045 .load_group(group_id)?
1046 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1047
1048 let member_indices: Vec<LeafNodeIndex> = group
1049 .members()
1050 .filter(|m| m.credential.serialized_content() == member_id.as_bytes())
1051 .map(|m| m.index)
1052 .collect();
1053
1054 if member_indices.is_empty() {
1055 return Err(MlsError::UserNotInGroup(member_id.to_string()));
1056 }
1057
1058 let signature_keys = self.get_signer()?;
1059 let commit =
1060 self.group_manager
1061 .remove_member(&mut group, &member_indices, &signature_keys)?;
1062
1063 self.group_manager.save_group(group_id, &group)?;
1064
1065 let ciphertext = commit
1066 .tls_serialize_detached()
1067 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1068
1069 Ok(EncryptedMessage {
1070 group_id: group_id.clone(),
1071 message_type: MlsMessageType::Commit,
1072 epoch: group.epoch().as_u64(),
1073 ciphertext,
1074 sender_id: self.user_id.clone(),
1075 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1076 })
1077 }
1078
1079 pub fn leave_group(&self, group_id: &GroupId) -> Result<()> {
1081 self.group_manager.delete_group(group_id)?;
1082 info!(group_id = %group_id, "Left group");
1083 Ok(())
1084 }
1085
1086 pub fn encrypt_for_group(
1088 &self,
1089 group_id: &GroupId,
1090 plaintext: &[u8],
1091 ) -> Result<EncryptedMessage> {
1092 let mut group = self
1093 .group_manager
1094 .load_group(group_id)?
1095 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1096
1097 let signature_keys = self.get_signer()?;
1098 let mls_message =
1099 self.group_manager
1100 .encrypt_message(&mut group, plaintext, &signature_keys)?;
1101
1102 self.group_manager.save_group(group_id, &group)?;
1103
1104 let ciphertext = mls_message
1105 .tls_serialize_detached()
1106 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1107
1108 Ok(EncryptedMessage {
1109 group_id: group_id.clone(),
1110 message_type: MlsMessageType::Application,
1111 epoch: group.epoch().as_u64(),
1112 ciphertext,
1113 sender_id: self.user_id.clone(),
1114 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1115 })
1116 }
1117
1118 pub fn decrypt_from_group(
1126 &self,
1127 encrypted: &EncryptedMessage,
1128 claimed_sender: &str,
1129 ) -> Result<Option<Vec<u8>>> {
1130 let mut group = self
1131 .group_manager
1132 .load_group(&encrypted.group_id)?
1133 .ok_or_else(|| MlsError::GroupNotFound(encrypted.group_id.to_string()))?;
1134
1135 let mls_message = MlsMessageIn::tls_deserialize_exact(&encrypted.ciphertext)
1136 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1137
1138 let result = self.group_manager.decrypt_message(
1139 &mut group,
1140 &encrypted.group_id,
1141 mls_message,
1142 claimed_sender,
1143 )?;
1144
1145 self.group_manager.save_group(&encrypted.group_id, &group)?;
1146
1147 Ok(result)
1148 }
1149
1150 pub fn join_group(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1152 if welcome.group_id.as_str().starts_with("session:") {
1164 return Err(MlsError::ReservedSessionNamespace {
1165 group_id: welcome.group_id.to_string(),
1166 });
1167 }
1168
1169 let mls_msg = MlsMessageIn::tls_deserialize_exact(&welcome.welcome_data)
1170 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1171
1172 let welcome_msg = match mls_msg.extract() {
1173 MlsMessageBodyIn::Welcome(w) => w,
1174 _ => {
1175 return Err(MlsError::WelcomeProcessing(
1176 "Not a Welcome message".to_string(),
1177 ))
1178 }
1179 };
1180
1181 let group = self
1182 .group_manager
1183 .join_group(welcome_msg, &welcome.group_id)?;
1184 let mut info = self.group_manager.get_group_info(&group, &welcome.group_id);
1185 info.name = welcome.group_name.clone();
1186
1187 info!(group_id = %welcome.group_id, "Joined group");
1188 Ok(info)
1189 }
1190
1191 pub fn list_groups(&self) -> Result<Vec<GroupId>> {
1193 let all_groups = self.group_manager.list_groups()?;
1194 Ok(all_groups
1195 .into_iter()
1196 .filter(|g| !g.as_str().starts_with("session:"))
1197 .collect())
1198 }
1199
1200 pub fn get_group_info(&self, group_id: &GroupId) -> Result<Option<GroupInfo>> {
1202 let group = match self.group_manager.load_group(group_id)? {
1203 Some(g) => g,
1204 None => return Ok(None),
1205 };
1206
1207 let mut info = self.group_manager.get_group_info(&group, group_id);
1208
1209 if let Some(metadata) = self.load_group_metadata(group_id)? {
1211 info.name = metadata.name;
1212 info.created_at_ms = metadata.created_at_ms;
1213 info.last_activity_ms = metadata.last_activity_ms;
1214 }
1215
1216 Ok(Some(info))
1217 }
1218
1219 pub fn has_group(&self, group_id: &GroupId) -> Result<bool> {
1228 Ok(self.group_manager.load_group(group_id)?.is_some())
1229 }
1230
1231 pub fn set_group_name(&self, group_id: &GroupId, name: &str) -> Result<()> {
1233 let mut metadata = self
1234 .load_group_metadata(group_id)?
1235 .unwrap_or_else(|| GroupMetadata::new(None));
1236 metadata.name = Some(name.to_string());
1237 metadata.touch();
1238 self.save_group_metadata(group_id, &metadata)
1239 }
1240
1241 pub fn get_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1243 self.load_group_metadata(group_id)
1244 }
1245
1246 pub fn set_group_custom_metadata(
1248 &self,
1249 group_id: &GroupId,
1250 key: &str,
1251 value: &str,
1252 ) -> Result<()> {
1253 let mut metadata = self
1254 .load_group_metadata(group_id)?
1255 .unwrap_or_else(|| GroupMetadata::new(None));
1256 metadata.custom.insert(key.to_string(), value.to_string());
1257 metadata.touch();
1258 self.save_group_metadata(group_id, &metadata)
1259 }
1260
1261 pub fn remove_group_custom_metadata(&self, group_id: &GroupId, key: &str) -> Result<()> {
1263 let mut metadata = self
1264 .load_group_metadata(group_id)?
1265 .unwrap_or_else(|| GroupMetadata::new(None));
1266 metadata.custom.remove(key);
1267 metadata.touch();
1268 self.save_group_metadata(group_id, &metadata)
1269 }
1270
1271 pub fn set_member_role(
1273 &self,
1274 group_id: &GroupId,
1275 user_id: &str,
1276 role: GroupRole,
1277 ) -> Result<()> {
1278 let mut metadata = self
1279 .load_group_metadata(group_id)?
1280 .unwrap_or_else(|| GroupMetadata::new(None));
1281 metadata.set_role(user_id, role);
1282 metadata.touch();
1283 self.save_group_metadata(group_id, &metadata)
1284 }
1285
1286 pub fn set_enforce_admin_commits(&mut self, enforce: bool) {
1295 self.group_manager.set_enforce_admin_commits(enforce);
1296 }
1297
1298 pub fn set_group_creator(&self, group_id: &GroupId, creator_id: &str) -> Result<()> {
1312 let mut metadata = self
1313 .load_group_metadata(group_id)?
1314 .unwrap_or_else(|| GroupMetadata::new(None));
1315 if metadata.created_by.is_some() {
1316 return Ok(());
1317 }
1318 metadata.created_by = Some(creator_id.to_string());
1319 metadata.touch();
1320 self.save_group_metadata(group_id, &metadata)
1321 }
1322
1323 pub fn remove_member_role(&self, group_id: &GroupId, user_id: &str) -> Result<()> {
1325 let mut metadata = self
1326 .load_group_metadata(group_id)?
1327 .unwrap_or_else(|| GroupMetadata::new(None));
1328 metadata.remove_role(user_id);
1329 metadata.touch();
1330 self.save_group_metadata(group_id, &metadata)
1331 }
1332
1333 pub fn decrypt(
1343 &self,
1344 encrypted: &EncryptedMessage,
1345 claimed_sender: &str,
1346 ) -> Result<Option<Vec<u8>>> {
1347 if encrypted.group_id.as_str().starts_with("session:") {
1348 self.decrypt_from_user(encrypted, claimed_sender)
1349 } else {
1350 self.decrypt_from_group(encrypted, claimed_sender)
1351 }
1352 }
1353
1354 pub fn process_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1356 if welcome.group_id.as_str().starts_with("session:") {
1357 self.join_session(welcome)
1358 } else {
1359 self.join_group(welcome)
1360 }
1361 }
1362}
1363
1364impl MlsManager {
1365 fn load_stored_key_package(&self, package_id: &str) -> Result<Option<KeyPackageBundle>> {
1371 let key_type = StorageKeyType::KeyPackage.as_str();
1372 let data = match self.storage.load(key_type, package_id)? {
1373 Some(data) => data,
1374 None => return Ok(None),
1375 };
1376
1377 let mut bundle = match serde_json::from_slice::<KeyPackageBundle>(&data) {
1378 Ok(bundle) => bundle,
1379 Err(_) => {
1380 let legacy_bundle = KeyPackageBundle::new(
1381 package_id.to_string(),
1382 self.user_id.clone(),
1383 data,
1384 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
1385 );
1386 let serialized = serde_json::to_vec(&legacy_bundle)
1387 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1388 self.storage.store(key_type, package_id, &serialized)?;
1389 legacy_bundle
1390 }
1391 };
1392
1393 if bundle.is_expired() {
1394 if bundle.expired_past_grace(KEY_PACKAGE_PURGE_GRACE_SECS) {
1401 self.purge_key_package_material(package_id, &bundle)?;
1402 }
1403 return Ok(None);
1404 }
1405
1406 let needs_backfill = bundle.provider_hash_ref.is_none();
1412 let Some(hash_ref) = self.bundle_hash_ref(&bundle) else {
1413 warn!(
1414 package_id = %package_id,
1415 "Key package bytes no longer parse as a key package, pruning stale entry"
1416 );
1417 self.storage.delete(key_type, package_id)?;
1418 return Ok(None);
1419 };
1420 if !self.provider_has_init_key(&hash_ref) {
1421 warn!(
1422 package_id = %package_id,
1423 "Key package private key no longer in provider storage, pruning stale entry"
1424 );
1425 self.storage.delete(key_type, package_id)?;
1426 return Ok(None);
1427 }
1428
1429 if needs_backfill {
1430 if let Ok(bytes) = hash_ref.tls_serialize_detached() {
1431 bundle.provider_hash_ref = Some(bytes);
1432 let serialized = serde_json::to_vec(&bundle)
1433 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1434 self.storage.store(key_type, package_id, &serialized)?;
1435 }
1436 }
1437
1438 Ok(Some(bundle))
1439 }
1440
1441 fn purge_key_package_material(
1456 &self,
1457 package_id: &str,
1458 bundle: &KeyPackageBundle,
1459 ) -> Result<()> {
1460 if let Some(hash_ref) = self.bundle_hash_ref(bundle) {
1461 use openmls_traits::storage::StorageProvider;
1462 if let Err(e) = self.provider.storage().delete_key_package(&hash_ref) {
1463 warn!(
1464 package_id = %package_id,
1465 error = %e,
1466 "Failed to delete a key package's private init key; keeping the record to retry"
1467 );
1468 return Ok(());
1469 }
1470 }
1471
1472 self.storage
1473 .delete(StorageKeyType::KeyPackage.as_str(), package_id)?;
1474 debug!(package_id = %package_id, "Purged key package and its init key");
1475 Ok(())
1476 }
1477
1478 fn key_package_hash_ref(&self, key_package_data: &[u8]) -> Option<KeyPackageRef> {
1485 let kp_in = KeyPackageIn::tls_deserialize_exact(key_package_data).ok()?;
1486 let kp: KeyPackage = kp_in
1487 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
1488 .ok()?;
1489 kp.hash_ref(self.provider.crypto()).ok()
1490 }
1491
1492 fn bundle_hash_ref(&self, bundle: &KeyPackageBundle) -> Option<KeyPackageRef> {
1496 bundle
1497 .provider_hash_ref
1498 .as_deref()
1499 .and_then(|bytes| KeyPackageRef::tls_deserialize_exact(bytes).ok())
1500 .or_else(|| self.key_package_hash_ref(&bundle.key_package_data))
1501 }
1502
1503 fn provider_has_init_key(&self, hash_ref: &KeyPackageRef) -> bool {
1507 use openmls_traits::storage::StorageProvider;
1508 let found: std::result::Result<Option<openmls::key_packages::KeyPackageBundle>, _> =
1509 self.provider.storage().key_package(hash_ref);
1510 matches!(found, Ok(Some(_)))
1511 }
1512
1513 #[cfg(test)]
1518 fn is_key_package_usable(&self, key_package_data: &[u8]) -> bool {
1519 self.key_package_hash_ref(key_package_data)
1520 .map(|hash_ref| self.provider_has_init_key(&hash_ref))
1521 .unwrap_or(false)
1522 }
1523
1524 fn load_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1526 let key_type = StorageKeyType::GroupMetadata.as_str();
1527 match self.storage.load(key_type, group_id.as_str())? {
1528 Some(data) => {
1529 let mut metadata: GroupMetadata = serde_json::from_slice(&data)
1530 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1531 if metadata.roles.is_empty()
1533 && metadata
1534 .custom
1535 .keys()
1536 .any(|k| k.starts_with(GroupMetadata::LEGACY_ROLE_KEY_PREFIX))
1537 {
1538 metadata.migrate_legacy_roles();
1539 if let Err(e) = self.save_group_metadata(group_id, &metadata) {
1541 warn!(group_id = %group_id.as_str(), error = %e, "Failed to persist legacy role migration");
1542 }
1543 }
1544 Ok(Some(metadata))
1545 }
1546 None => Ok(None),
1547 }
1548 }
1549
1550 fn save_group_metadata(&self, group_id: &GroupId, metadata: &GroupMetadata) -> Result<()> {
1552 let key_type = StorageKeyType::GroupMetadata.as_str();
1553 let data =
1554 serde_json::to_vec(metadata).map_err(|e| MlsError::Serialization(e.to_string()))?;
1555 self.storage.store(key_type, group_id.as_str(), &data)?;
1556 Ok(())
1557 }
1558}
1559
1560impl MlsManager {
1565 pub fn update_keys(&self, group_id: &GroupId) -> Result<EncryptedMessage> {
1574 use openmls::treesync::LeafNodeParameters;
1575
1576 let mut group = self
1577 .group_manager
1578 .load_group(group_id)?
1579 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1580
1581 let signature_keys = self.get_signer()?;
1582
1583 let bundle = group
1584 .self_update(
1585 &self.provider,
1586 &signature_keys,
1587 LeafNodeParameters::default(),
1588 )
1589 .map_err(|e| MlsError::OpenMls(format!("Self-update failed: {}", e)))?;
1590
1591 let (commit, _welcome, _group_info) = bundle.into_contents();
1592
1593 group
1594 .merge_pending_commit(&self.provider)
1595 .map_err(|e| MlsError::OpenMls(format!("Failed to merge self-update commit: {}", e)))?;
1596
1597 self.group_manager.save_group(group_id, &group)?;
1598
1599 if let Some(mut metadata) = self.load_group_metadata(group_id)? {
1601 metadata.touch();
1602 self.save_group_metadata(group_id, &metadata)?;
1603 }
1604
1605 let ciphertext = commit
1606 .tls_serialize_detached()
1607 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1608
1609 debug!(group_id = %group_id, epoch = %group.epoch().as_u64(), "Updated group keys");
1610
1611 Ok(EncryptedMessage {
1612 group_id: group_id.clone(),
1613 message_type: MlsMessageType::Commit,
1614 epoch: group.epoch().as_u64(),
1615 ciphertext,
1616 sender_id: self.user_id.clone(),
1617 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1618 })
1619 }
1620
1621 pub fn ensure_min_key_packages(&self, min: usize) -> Result<usize> {
1640 let min = min.min(MAX_PUSH_KEY_PACKAGES);
1641 let key_type = StorageKeyType::KeyPackage.as_str();
1642 let package_ids = self.storage.list_keys(key_type)?;
1643
1644 let mut valid_count = 0;
1646 for package_id in &package_ids {
1647 if self.load_stored_key_package(package_id)?.is_some() {
1648 valid_count += 1;
1649 }
1650 }
1651
1652 let to_generate = min.saturating_sub(valid_count);
1654 for _ in 0..to_generate {
1655 self.generate_key_package()?;
1656 valid_count += 1;
1657 }
1658
1659 debug!(
1660 valid_count = valid_count,
1661 generated = to_generate,
1662 "Ensured minimum key packages"
1663 );
1664
1665 Ok(valid_count)
1666 }
1667
1668 pub fn count_valid_key_packages(&self) -> Result<usize> {
1670 let key_type = StorageKeyType::KeyPackage.as_str();
1671 let package_ids = self.storage.list_keys(key_type)?;
1672
1673 let mut count = 0;
1674 for package_id in package_ids {
1675 if self.load_stored_key_package(&package_id)?.is_some() {
1676 count += 1;
1677 }
1678 }
1679
1680 Ok(count)
1681 }
1682
1683 pub fn get_identity_public_key(&self) -> Result<Vec<u8>> {
1692 let credential = self.get_credential()?;
1693 Ok(credential.signature_key.as_slice().to_vec())
1694 }
1695
1696 pub fn sign_data(&self, data: &[u8]) -> Result<Vec<u8>> {
1709 use openmls_traits::signatures::Signer;
1710
1711 let signer = self.get_signer()?;
1712 let signature = signer
1713 .sign(data)
1714 .map_err(|e| MlsError::Signing(format!("Failed to sign data: {:?}", e)))?;
1715 Ok(signature.as_slice().to_vec())
1716 }
1717
1718 pub fn verify_signature(public_key: &[u8], data: &[u8], signature: &[u8]) -> Result<bool> {
1730 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
1731
1732 let verifying_key = VerifyingKey::try_from(public_key).map_err(|e| {
1734 MlsError::InvalidPublicKey(format!("Invalid Ed25519 public key: {}", e))
1735 })?;
1736
1737 let sig = Signature::try_from(signature).map_err(|e| {
1739 MlsError::VerificationFailed(format!("Invalid signature format: {}", e))
1740 })?;
1741
1742 match verifying_key.verify(data, &sig) {
1744 Ok(()) => Ok(true),
1745 Err(_) => Ok(false),
1746 }
1747 }
1748
1749 pub fn derive_address(public_key: &[u8]) -> Result<Address> {
1772 use sha2::{Digest, Sha256};
1773
1774 if public_key.len() != ED25519_PUBLIC_KEY_LEN {
1775 return Err(MlsError::InvalidPublicKey(format!(
1776 "Ed25519 public key must be {} bytes, got {}",
1777 ED25519_PUBLIC_KEY_LEN,
1778 public_key.len()
1779 )));
1780 }
1781
1782 let hash = Sha256::digest(public_key);
1783 let mut truncated = [0u8; Address::HASH_LEN];
1784 truncated.copy_from_slice(&hash[..Address::HASH_LEN]);
1785 Ok(Address::from_hash_bytes(truncated))
1786 }
1787
1788 #[deprecated(
1802 note = "use derive_address; this returns the self-certifying off1… address, no longer base58"
1803 )]
1804 pub fn derive_user_id_from_public_key(public_key: &[u8]) -> String {
1805 use sha2::{Digest, Sha256};
1806
1807 let hash = Sha256::digest(public_key);
1808 let mut truncated = [0u8; Address::HASH_LEN];
1809 truncated.copy_from_slice(&hash[..Address::HASH_LEN]);
1810 Address::from_hash_bytes(truncated).to_string()
1811 }
1812}
1813
1814#[cfg(feature = "test-utils")]
1823impl MlsManager {
1824 pub fn seat_forged_leaf_for_testing(
1838 &self,
1839 group_id: &GroupId,
1840 impersonated: &str,
1841 ) -> Result<Vec<u8>> {
1842 let forged_storage: Arc<dyn MlsStorage> = Arc::new(crate::storage::InMemoryStorage::new());
1843 let forged_provider = MlsProvider::new(MlsStorageAdapter::new(forged_storage));
1844 let forged_keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
1845 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
1846 forged_keys
1847 .store(forged_provider.storage())
1848 .map_err(|e| MlsError::CryptoGeneration(format!("storing forged key: {:?}", e)))?;
1849
1850 let credential = CredentialWithKey {
1851 credential: Credential::new(CredentialType::Basic, impersonated.as_bytes().to_vec()),
1852 signature_key: forged_keys.public().into(),
1853 };
1854 let key_package = KeyPackage::builder()
1855 .build(
1856 DEFAULT_CIPHERSUITE,
1857 &forged_provider,
1858 &forged_keys,
1859 credential,
1860 )
1861 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?
1862 .key_package()
1863 .tls_serialize_detached()
1864 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1865
1866 let validated = KeyPackageIn::tls_deserialize_exact(&key_package)
1867 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?
1868 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
1869 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
1870
1871 let mut group = self
1872 .group_manager
1873 .load_group(group_id)?
1874 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1875 let signer = self.get_signer()?;
1876 let (commit, _welcome) = self
1877 .group_manager
1878 .add_member(&mut group, validated, &signer)?;
1879 self.group_manager.save_group(group_id, &group)?;
1880
1881 commit
1882 .tls_serialize_detached()
1883 .map_err(|e| MlsError::Serialization(e.to_string()))
1884 }
1885}
1886
1887pub fn is_mls_framed(bytes: &[u8]) -> bool {
1896 MlsMessageIn::tls_deserialize_exact(bytes).is_ok()
1897}
1898
1899#[cfg(test)]
1900mod tests {
1901 use super::*;
1902 use crate::storage::InMemoryStorage;
1903
1904 fn create_test_manager(label: &str) -> MlsManager {
1913 create_addressed_manager(label).0
1914 }
1915
1916 fn addr(label: &str) -> String {
1918 test_identity(label).1.to_string()
1919 }
1920
1921 fn slot(a: &str, b: &str) -> GroupId {
1927 GroupId::for_session(&addr(a), &addr(b)).unwrap()
1928 }
1929
1930 pub(crate) fn test_identity(label: &str) -> (SignatureKeyPair, Address) {
1938 use sha2::{Digest, Sha256};
1939
1940 let seed: [u8; 32] = Sha256::digest(label.as_bytes()).into();
1941 let signing = ed25519_dalek::SigningKey::from_bytes(&seed);
1942 let public = signing.verifying_key().to_bytes().to_vec();
1943 let address = MlsManager::derive_address(&public).unwrap();
1944 let keys = SignatureKeyPair::from_raw(
1945 DEFAULT_CIPHERSUITE.signature_algorithm(),
1946 signing.to_bytes().to_vec(),
1947 public,
1948 );
1949 (keys, address)
1950 }
1951
1952 pub(crate) fn seed_identity(storage: &Arc<dyn MlsStorage>, keys: &SignatureKeyPair) {
1955 storage
1956 .store(
1957 StorageKeyType::Identity.as_str(),
1958 "key_pair",
1959 &serde_json::to_vec(keys).unwrap(),
1960 )
1961 .unwrap();
1962 }
1963
1964 fn create_addressed_manager(label: &str) -> (MlsManager, Address) {
1967 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
1968 let (keys, address) = test_identity(label);
1969 seed_identity(&storage, &keys);
1970 let manager = MlsManager::new(address.to_string(), storage).unwrap();
1971 (manager, address)
1972 }
1973
1974 fn create_test_manager_with_storage(user_id: &str) -> (MlsManager, Arc<InMemoryStorage>) {
1978 let storage = Arc::new(InMemoryStorage::new());
1979 let manager = MlsManager::new(user_id, storage.clone()).unwrap();
1980 (manager, storage)
1981 }
1982
1983 fn expire_package(storage: &InMemoryStorage, package_id: &str, secs_ago: u64) {
1985 let key_type = StorageKeyType::KeyPackage.as_str();
1986 let data = storage
1987 .load(key_type, package_id)
1988 .unwrap()
1989 .expect("package to age is not in storage");
1990 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
1991 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
1992 bundle.expires_at_ms = now_ms - (secs_ago * 1000);
1993 storage
1994 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
1995 .unwrap();
1996 }
1997
1998 fn init_key_present(manager: &MlsManager, bundle: &KeyPackageBundle) -> bool {
2000 manager.is_key_package_usable(&bundle.key_package_data)
2001 }
2002
2003 fn strip_cached_ref(storage: &InMemoryStorage, package_id: &str) {
2006 let key_type = StorageKeyType::KeyPackage.as_str();
2007 let data = storage
2008 .load(key_type, package_id)
2009 .unwrap()
2010 .expect("package to strip is not in storage");
2011 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2012 bundle.provider_hash_ref = None;
2013 storage
2014 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2015 .unwrap();
2016 }
2017
2018 #[test]
2023 fn test_minted_package_carries_its_provider_hash_ref() {
2024 let manager = create_test_manager("alice");
2025 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2026
2027 let cached = bundle
2028 .provider_hash_ref
2029 .as_deref()
2030 .expect("mint must stamp the provider hash ref");
2031 let derived = manager
2032 .key_package_hash_ref(&bundle.key_package_data)
2033 .expect("freshly minted package bytes must parse")
2034 .tls_serialize_detached()
2035 .unwrap();
2036 assert_eq!(
2037 cached,
2038 &derived[..],
2039 "cached ref disagrees with the one derived from the package bytes"
2040 );
2041 }
2042
2043 #[test]
2046 fn test_record_without_cached_ref_is_backfilled_on_first_load() {
2047 let (manager, storage) = create_test_manager_with_storage("alice");
2048 let minted = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2049 strip_cached_ref(&storage, &minted.package_id);
2050
2051 let loaded = manager
2052 .key_package_by_id(&minted.package_id)
2053 .unwrap()
2054 .expect("a record without a cached ref must still load");
2055 assert_eq!(
2056 loaded.provider_hash_ref, minted.provider_hash_ref,
2057 "load must hand back a bundle stamped with the same ref the mint computed"
2058 );
2059
2060 let stored: KeyPackageBundle = serde_json::from_slice(
2061 &storage
2062 .load(StorageKeyType::KeyPackage.as_str(), &minted.package_id)
2063 .unwrap()
2064 .unwrap(),
2065 )
2066 .unwrap();
2067 assert_eq!(
2068 stored.provider_hash_ref, minted.provider_hash_ref,
2069 "the backfilled ref must be persisted, not only returned"
2070 );
2071 }
2072
2073 #[test]
2076 fn test_purge_destroys_init_key_for_a_record_without_cached_ref() {
2077 let (manager, storage) = create_test_manager_with_storage("alice");
2078 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2079 strip_cached_ref(&storage, &bundle.package_id);
2080 expire_package(
2081 &storage,
2082 &bundle.package_id,
2083 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2084 );
2085
2086 assert!(manager
2087 .key_package_by_id(&bundle.package_id)
2088 .unwrap()
2089 .is_none());
2090 assert!(
2091 !init_key_present(&manager, &bundle),
2092 "the private init key outlived a pre-cache package"
2093 );
2094 }
2095
2096 #[test]
2101 fn test_expired_package_is_withdrawn_but_its_init_key_survives_the_grace_window() {
2102 let (manager, storage) = create_test_manager_with_storage("alice");
2103 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2104
2105 expire_package(&storage, &bundle.package_id, 60);
2106
2107 assert!(
2108 manager
2109 .key_package_by_id(&bundle.package_id)
2110 .unwrap()
2111 .is_none(),
2112 "an expired package must not be handed out"
2113 );
2114 assert!(
2115 init_key_present(&manager, &bundle),
2116 "the init key was destroyed on the expiry edge, breaking a late Welcome"
2117 );
2118
2119 let replacement = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2121 assert_ne!(replacement.package_id, bundle.package_id);
2122 }
2123
2124 #[test]
2129 fn test_package_past_the_grace_window_has_its_init_key_destroyed() {
2130 let (manager, storage) = create_test_manager_with_storage("alice");
2131 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2132 assert!(init_key_present(&manager, &bundle));
2133
2134 expire_package(
2135 &storage,
2136 &bundle.package_id,
2137 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2138 );
2139
2140 assert!(manager
2142 .key_package_by_id(&bundle.package_id)
2143 .unwrap()
2144 .is_none());
2145
2146 assert!(
2147 !init_key_present(&manager, &bundle),
2148 "the private init key outlived its package"
2149 );
2150 assert!(
2151 storage
2152 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2153 .unwrap()
2154 .is_none(),
2155 "the bundle record outlived its key material"
2156 );
2157 }
2158
2159 #[test]
2163 fn test_delete_key_package_destroys_the_init_key() {
2164 let manager = create_test_manager("alice");
2165 let bundle = manager.generate_publication_key_package().unwrap();
2166 assert!(init_key_present(&manager, &bundle));
2167
2168 manager.delete_key_package(&bundle.package_id).unwrap();
2169
2170 assert!(
2171 !init_key_present(&manager, &bundle),
2172 "delete_key_package left the private init key in provider storage"
2173 );
2174 }
2175
2176 #[test]
2181 fn test_delete_key_package_destroys_the_init_key_of_a_legacy_record() {
2182 let (manager, storage) = create_test_manager_with_storage("alice");
2183 let bundle = manager.generate_publication_key_package().unwrap();
2184 storage
2186 .store(
2187 StorageKeyType::KeyPackage.as_str(),
2188 &bundle.package_id,
2189 &bundle.key_package_data,
2190 )
2191 .unwrap();
2192
2193 manager.delete_key_package(&bundle.package_id).unwrap();
2194
2195 assert!(
2196 !init_key_present(&manager, &bundle),
2197 "a legacy record's private init key survived its deletion"
2198 );
2199 assert!(
2200 storage
2201 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2202 .unwrap()
2203 .is_none(),
2204 "the legacy record outlived its key material"
2205 );
2206 }
2207
2208 #[test]
2212 fn test_delete_key_package_removes_an_unreadable_record() {
2213 let (manager, storage) = create_test_manager_with_storage("alice");
2214 storage
2215 .store(
2216 StorageKeyType::KeyPackage.as_str(),
2217 "junk",
2218 b"not a package",
2219 )
2220 .unwrap();
2221
2222 manager.delete_key_package("junk").unwrap();
2223
2224 assert!(storage
2225 .load(StorageKeyType::KeyPackage.as_str(), "junk")
2226 .unwrap()
2227 .is_none());
2228 }
2229
2230 #[test]
2236 fn test_ensure_min_key_packages_is_capped_at_the_pool_ceiling() {
2237 let manager = create_test_manager("alice");
2238
2239 let count = manager
2240 .ensure_min_key_packages(MAX_PUSH_KEY_PACKAGES + 50)
2241 .unwrap();
2242
2243 assert_eq!(count, MAX_PUSH_KEY_PACKAGES);
2244 assert_eq!(
2245 manager.count_valid_key_packages().unwrap(),
2246 MAX_PUSH_KEY_PACKAGES,
2247 "pre-filling minted past the ceiling the push path enforces"
2248 );
2249 }
2250
2251 #[test]
2252 fn test_manager_creation() {
2253 let manager = create_test_manager("alice");
2254 assert_eq!(manager.user_id(), addr("alice"));
2255 }
2256
2257 #[test]
2258 fn test_key_package_generation() {
2259 let manager = create_test_manager("alice");
2260 let package = manager.generate_key_package().unwrap();
2261
2262 assert_eq!(package.user_id, addr("alice"));
2263 assert!(!package.key_package_data.is_empty());
2264 assert!(!package.is_expired());
2265 }
2266
2267 #[test]
2272 fn test_get_or_create_never_returns_a_publication_package() {
2273 let manager = create_test_manager("alice");
2274 let reserved = manager.generate_publication_key_package().unwrap();
2275 assert!(reserved.reserved_for_publication);
2276
2277 let pushed = manager.get_or_create_key_package().unwrap();
2278 assert_ne!(
2279 pushed.package_id, reserved.package_id,
2280 "the push path took the package a published record is standing on"
2281 );
2282 assert!(!pushed.reserved_for_publication);
2283
2284 let pushed_again = manager.get_or_create_key_package().unwrap();
2287 assert_eq!(pushed.package_id, pushed_again.package_id);
2288 }
2289
2290 #[test]
2294 fn test_key_package_by_id_reports_presence() {
2295 let manager = create_test_manager("alice");
2296 let bundle = manager.generate_publication_key_package().unwrap();
2297
2298 let found = manager.key_package_by_id(&bundle.package_id).unwrap();
2299 assert!(found.is_some());
2300 assert_eq!(found.unwrap().package_id, bundle.package_id);
2301
2302 assert!(manager
2303 .key_package_by_id("no-such-package")
2304 .unwrap()
2305 .is_none());
2306 }
2307
2308 #[test]
2311 fn test_publication_packages_are_distinct_per_slot() {
2312 let manager = create_test_manager("alice");
2313 let a = manager.generate_publication_key_package().unwrap();
2314 let b = manager.generate_publication_key_package().unwrap();
2315 assert_ne!(a.package_id, b.package_id);
2316 assert_ne!(a.key_package_data, b.key_package_data);
2317 }
2318
2319 #[test]
2320 fn test_get_or_create_key_package() {
2321 let manager = create_test_manager("alice");
2322 let pkg1 = manager.get_or_create_key_package().unwrap();
2323 let pkg2 = manager.get_or_create_key_package().unwrap();
2324 assert_eq!(pkg1.package_id, pkg2.package_id);
2328 }
2329
2330 #[test]
2340 fn test_push_path_gives_each_peer_its_own_key_package() {
2341 let manager = create_test_manager("alice");
2342
2343 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap();
2344 let for_carol = manager.take_push_key_package(&addr("carol")).unwrap();
2345
2346 assert_ne!(
2347 for_bob.bundle.package_id, for_carol.bundle.package_id,
2348 "two peers were advertised the same key package"
2349 );
2350 assert_ne!(
2351 for_bob.bundle.key_package_data, for_carol.bundle.key_package_data,
2352 "two peers were advertised the same init key"
2353 );
2354 assert!(!for_bob.pool_exhausted && !for_carol.pool_exhausted);
2355 assert_eq!(
2356 for_bob.bundle.assigned_peer.as_deref(),
2357 Some(addr("bob").as_str())
2358 );
2359 assert_eq!(
2360 for_carol.bundle.assigned_peer.as_deref(),
2361 Some(addr("carol").as_str())
2362 );
2363 }
2364
2365 #[test]
2370 fn test_push_path_reuses_one_package_for_repeat_pushes_to_a_peer() {
2371 let manager = create_test_manager("alice");
2372
2373 let first = manager.take_push_key_package(&addr("bob")).unwrap();
2374 let second = manager.take_push_key_package(&addr("bob")).unwrap();
2375 let third = manager.take_push_key_package(&addr("bob")).unwrap();
2376
2377 assert_eq!(first.bundle.package_id, second.bundle.package_id);
2378 assert_eq!(first.bundle.package_id, third.bundle.package_id);
2379 assert_eq!(
2380 manager.count_valid_key_packages().unwrap(),
2381 1,
2382 "repeat pushes to one peer minted extra key material"
2383 );
2384 }
2385
2386 #[test]
2391 fn test_push_path_mints_a_fresh_package_after_the_peer_consumed_one() {
2392 let alice = create_test_manager("alice");
2393 let bob = create_test_manager("bob");
2394
2395 let advertised = alice.take_push_key_package(&addr("bob")).unwrap().bundle;
2396 bob.import_key_package(&addr("alice"), &advertised.key_package_data)
2397 .unwrap();
2398 let welcome = bob.create_session(&addr("alice")).unwrap();
2399 alice.join_session(&welcome).unwrap();
2400
2401 let next = alice.take_push_key_package(&addr("bob")).unwrap();
2402 assert_ne!(
2403 next.bundle.package_id, advertised.package_id,
2404 "a consumed package was advertised again"
2405 );
2406 assert!(!next.pool_exhausted);
2407 }
2408
2409 #[test]
2414 fn test_two_peers_can_both_establish_sessions_after_being_pushed_to() {
2415 let alice = create_test_manager("alice");
2416 let bob = create_test_manager("bob");
2417 let carol = create_test_manager("carol");
2418
2419 for (peer, manager) in [(addr("bob"), &bob), (addr("carol"), &carol)] {
2420 let advertised = alice.take_push_key_package(&peer).unwrap().bundle;
2421 manager
2422 .import_key_package(&addr("alice"), &advertised.key_package_data)
2423 .unwrap();
2424 }
2425
2426 let from_bob = bob.create_session(&addr("alice")).unwrap();
2429 let from_carol = carol.create_session(&addr("alice")).unwrap();
2430
2431 alice.join_session(&from_bob).unwrap();
2432 alice
2433 .join_session(&from_carol)
2434 .expect("second peer's Welcome must still be processable");
2435
2436 assert!(alice.has_session(&addr("bob")).unwrap());
2437 assert!(alice.has_session(&addr("carol")).unwrap());
2438 }
2439
2440 #[test]
2443 fn test_push_path_never_takes_a_publication_package() {
2444 let manager = create_test_manager("alice");
2445 let reserved = manager.generate_publication_key_package().unwrap();
2446
2447 for peer in [addr("bob"), addr("carol"), addr("dave")] {
2448 let taken = manager.take_push_key_package(&peer).unwrap();
2449 assert_ne!(
2450 taken.bundle.package_id, reserved.package_id,
2451 "the push path took the package a published record stands on"
2452 );
2453 }
2454 }
2455
2456 #[test]
2460 fn test_get_or_create_never_returns_a_peer_assigned_package() {
2461 let manager = create_test_manager("alice");
2462 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2463
2464 let unclaimed = manager.get_or_create_key_package().unwrap();
2465 assert_ne!(unclaimed.package_id, for_bob.package_id);
2466 assert!(unclaimed.assigned_peer.is_none());
2467 }
2468
2469 #[test]
2473 fn test_push_path_claims_an_unclaimed_package_instead_of_stranding_it() {
2474 let manager = create_test_manager("alice");
2475 let legacy = manager.get_or_create_key_package().unwrap();
2476 assert!(legacy.assigned_peer.is_none());
2477
2478 let taken = manager.take_push_key_package(&addr("bob")).unwrap();
2479 assert_eq!(
2480 taken.bundle.package_id, legacy.package_id,
2481 "the pre-existing package was stranded instead of claimed"
2482 );
2483 assert_eq!(
2484 taken.bundle.assigned_peer.as_deref(),
2485 Some(addr("bob").as_str())
2486 );
2487 assert_eq!(manager.count_valid_key_packages().unwrap(), 1);
2488 }
2489
2490 #[test]
2495 fn test_push_pool_ceiling_shares_a_package_and_reports_it() {
2496 let manager = create_test_manager("alice");
2497
2498 for i in 0..MAX_PUSH_KEY_PACKAGES {
2499 let taken = manager.take_push_key_package(&format!("peer{i}")).unwrap();
2500 assert!(
2501 !taken.pool_exhausted,
2502 "pool reported exhausted at {i}, below the ceiling"
2503 );
2504 }
2505 assert_eq!(
2506 manager.count_valid_key_packages().unwrap(),
2507 MAX_PUSH_KEY_PACKAGES
2508 );
2509
2510 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
2511 assert!(over.pool_exhausted, "ceiling breach was not reported");
2512 assert_eq!(
2513 manager.count_valid_key_packages().unwrap(),
2514 MAX_PUSH_KEY_PACKAGES,
2515 "the pool grew past its ceiling"
2516 );
2517
2518 let established = manager.take_push_key_package("peer0").unwrap();
2521 assert!(!established.pool_exhausted);
2522 assert_eq!(established.bundle.assigned_peer.as_deref(), Some("peer0"));
2523 }
2524
2525 #[test]
2531 fn test_ceiling_claims_an_unclaimed_package_rather_than_sharing() {
2532 let manager = create_test_manager("alice");
2533
2534 for i in 0..(MAX_PUSH_KEY_PACKAGES - 1) {
2535 manager.take_push_key_package(&format!("peer{i}")).unwrap();
2536 }
2537 let unclaimed = manager.get_or_create_key_package().unwrap();
2540 assert_eq!(
2541 manager.count_valid_key_packages().unwrap(),
2542 MAX_PUSH_KEY_PACKAGES,
2543 "test premise: the pool is at the ceiling"
2544 );
2545
2546 let taken = manager.take_push_key_package("late-arrival").unwrap();
2547 assert!(
2548 !taken.pool_exhausted,
2549 "shared a package while a claimable one was in the pool"
2550 );
2551 assert_eq!(
2552 taken.bundle.package_id, unclaimed.package_id,
2553 "the claimable package was passed over"
2554 );
2555 assert_eq!(taken.bundle.assigned_peer.as_deref(), Some("late-arrival"));
2556 assert_eq!(
2557 manager.count_valid_key_packages().unwrap(),
2558 MAX_PUSH_KEY_PACKAGES,
2559 "claiming grew the pool"
2560 );
2561
2562 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
2565 assert!(over.pool_exhausted);
2566 }
2567
2568 #[test]
2569 fn test_import_key_package_binds_credential_identity() {
2570 let alice = create_test_manager("alice");
2571 let mallory = create_test_manager("mallory");
2572 let mallory_kp = mallory.generate_key_package().unwrap();
2573
2574 let err = alice
2577 .import_key_package(&addr("bob"), &mallory_kp.key_package_data)
2578 .unwrap_err();
2579 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
2580
2581 alice
2583 .import_key_package(&addr("mallory"), &mallory_kp.key_package_data)
2584 .unwrap();
2585 }
2586
2587 fn substituted_key_package(claimed: &str, keys: &SignatureKeyPair) -> Vec<u8> {
2597 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2598 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
2599 keys.store(provider.storage()).unwrap();
2600
2601 let credential = CredentialWithKey {
2602 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
2603 signature_key: keys.public().into(),
2604 };
2605
2606 KeyPackage::builder()
2607 .build(DEFAULT_CIPHERSUITE, &provider, keys, credential)
2608 .unwrap()
2609 .key_package()
2610 .tls_serialize_detached()
2611 .unwrap()
2612 }
2613
2614 #[test]
2615 fn test_credential_identity_check_alone_does_not_stop_a_substitution() {
2616 let (mallorys_keys, _) = test_identity("mallory");
2621 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
2622
2623 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2624 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
2625 let key_package = KeyPackageIn::tls_deserialize_exact(&forged)
2626 .unwrap()
2627 .validate(provider.crypto(), ProtocolVersion::Mls10)
2628 .unwrap();
2629
2630 MlsManager::verify_credential_identity(&key_package, &addr("bob"))
2631 .expect("the identity check cannot tell the two bobs apart");
2632 }
2633
2634 #[test]
2635 fn test_substituted_key_package_is_refused_by_the_derivation() {
2636 let alice = create_test_manager("alice");
2641 let real_bob = create_test_manager("bob");
2642 let (mallorys_keys, _) = test_identity("mallory");
2643
2644 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
2645 let err = alice.import_key_package(&addr("bob"), &forged).unwrap_err();
2646 assert!(
2647 matches!(
2648 err,
2649 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
2650 ),
2651 "expected an address mismatch, got {:?}",
2652 err
2653 );
2654
2655 let real_kp = real_bob.generate_key_package().unwrap();
2657 alice
2658 .import_key_package(&addr("bob"), &real_kp.key_package_data)
2659 .expect("the real peer's key package derives to its own address");
2660 }
2661
2662 #[test]
2663 fn test_a_nickname_peer_id_is_refused_rather_than_skipping_the_check() {
2664 let alice = create_test_manager("alice");
2668 let bob_kp = create_test_manager("bob").generate_key_package().unwrap();
2669
2670 let err = alice
2671 .import_key_package("bob", &bob_kp.key_package_data)
2672 .unwrap_err();
2673 assert!(
2674 matches!(
2675 err,
2676 MlsError::CredentialIdentityMismatch { .. } | MlsError::InvalidUserId(_)
2677 ),
2678 "a nickname must be refused, got {:?}",
2679 err
2680 );
2681 }
2682
2683 #[test]
2684 fn test_add_group_member_binds_identity_and_derivation() {
2685 let alice = create_test_manager("alice");
2689 let bob = create_test_manager("bob");
2690 let mallory = create_test_manager("mallory");
2691
2692 let group_id = alice.create_group("Test Group").unwrap().group_id;
2693
2694 let mallory_kp = mallory.generate_key_package().unwrap();
2696 let err = alice
2697 .add_group_member(&group_id, &addr("bob"), &mallory_kp.key_package_data)
2698 .unwrap_err();
2699 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
2700
2701 let (mallorys_keys, _) = test_identity("mallory");
2703 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
2704 let err = alice
2705 .add_group_member(&group_id, &addr("bob"), &forged)
2706 .unwrap_err();
2707 assert!(matches!(err, MlsError::KeyPackageAddressMismatch { .. }));
2708
2709 let bob_kp = bob.generate_key_package().unwrap();
2711 alice
2712 .add_group_member(&group_id, &addr("bob"), &bob_kp.key_package_data)
2713 .expect("the real invitee is admitted");
2714 }
2715
2716 #[test]
2717 fn test_import_key_package_rejects_hostile_user_id() {
2718 let alice = create_test_manager("alice");
2719 let bob = create_test_manager("bob");
2720 let bob_kp = bob.generate_key_package().unwrap();
2721
2722 for hostile in ["", "..", "bob/evil", "bob\\evil", "bob\0evil"] {
2723 let err = alice
2724 .import_key_package(hostile, &bob_kp.key_package_data)
2725 .unwrap_err();
2726 assert!(
2727 matches!(err, MlsError::InvalidUserId(_)),
2728 "expected InvalidUserId for {:?}, got {:?}",
2729 hostile,
2730 err
2731 );
2732 }
2733 }
2734
2735 #[test]
2736 fn test_get_contact_key_package_rejects_poisoned_store() {
2737 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2741 let (alice_keys, alice_address) = test_identity("alice");
2742 seed_identity(&storage, &alice_keys);
2743 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
2744 let mallory = create_test_manager("mallory");
2745 let mallory_kp = mallory.generate_key_package().unwrap();
2746
2747 storage
2748 .store(
2749 StorageKeyType::ContactKeyPackage.as_str(),
2750 &addr("bob"),
2751 &mallory_kp.key_package_data,
2752 )
2753 .unwrap();
2754
2755 let err = alice.create_session(&addr("bob")).unwrap_err();
2757 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
2758 }
2759
2760 #[test]
2779 fn test_get_contact_key_package_rejects_a_substituted_package() {
2780 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2781 let (alice_keys, alice_address) = test_identity("alice");
2782 seed_identity(&storage, &alice_keys);
2783 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
2784
2785 let (mallorys_keys, _) = test_identity("mallory");
2787 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
2788
2789 storage
2790 .store(
2791 StorageKeyType::ContactKeyPackage.as_str(),
2792 &addr("bob"),
2793 &forged,
2794 )
2795 .unwrap();
2796
2797 let err = alice.create_session(&addr("bob")).unwrap_err();
2798 assert!(
2799 matches!(
2800 err,
2801 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
2802 ),
2803 "a substituted cached package must be refused at use time, got {:?}",
2804 err
2805 );
2806
2807 assert!(!alice.has_session(&addr("bob")).unwrap());
2809 }
2810
2811 #[test]
2812 fn test_replace_session_with_welcome_rejects_hostile_inviter_id() {
2813 let alice = create_test_manager("alice");
2817 let welcome = WelcomeMessage {
2818 group_id: slot("alice", "bob"),
2819 welcome_data: vec![],
2820 inviter_id: "../../etc".to_string(),
2821 group_name: None,
2822 timestamp_ms: 0,
2823 };
2824 let err = alice.replace_session_with_welcome(&welcome).unwrap_err();
2825 assert!(matches!(err, MlsError::InvalidUserId(_)));
2826 }
2827
2828 fn create_test_session() -> (MlsManager, MlsManager) {
2830 let alice = create_test_manager("alice");
2831 let bob = create_test_manager("bob");
2832 let bob_kp = bob.generate_key_package().unwrap();
2833 alice
2834 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
2835 .unwrap();
2836 let welcome = alice.create_session(&addr("bob")).unwrap();
2837 bob.join_session(&welcome).unwrap();
2838 (alice, bob)
2839 }
2840
2841 #[test]
2842 fn test_session_decrypt_rejects_spoofed_sender() {
2843 let (alice, bob) = create_test_session();
2844
2845 let ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
2851 let err = bob.decrypt_from_user(&ct, &addr("mallory")).unwrap_err();
2852 assert!(
2853 matches!(err, MlsError::SessionIdentityMismatch { .. }),
2854 "spoofed sender must be rejected by the slot binding, got {:?}",
2855 err
2856 );
2857
2858 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
2863 assert_eq!(pt.as_deref(), Some(&b"hello"[..]));
2864
2865 }
2869
2870 #[test]
2871 fn test_epoch_fork_classifies_as_session_desync() {
2872 let (alice, bob) = create_test_session();
2877
2878 let group_id = slot("alice", "bob");
2881 alice.update_keys(&group_id).unwrap();
2882
2883 let ct = alice.encrypt_for_user(&addr("bob"), b"after-fork").unwrap();
2886 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
2887 assert!(
2888 matches!(err, MlsError::SessionDesync(_)),
2889 "epoch fork must classify as SessionDesync, got {:?}",
2890 err
2891 );
2892 }
2893
2894 fn push_varint(out: &mut Vec<u8>, len: usize) {
2907 match len {
2908 0..=63 => out.push(len as u8),
2909 64..=16_383 => {
2910 out.push(0x40 | (len >> 8) as u8);
2911 out.push((len & 0xff) as u8);
2912 }
2913 _ => panic!("test payloads stay under the 2-byte varint ceiling"),
2914 }
2915 }
2916
2917 fn forge_private_message(group_id: &str, epoch: u64) -> Vec<u8> {
2918 let mut out = Vec::new();
2919 out.extend_from_slice(&1u16.to_be_bytes()); out.extend_from_slice(&2u16.to_be_bytes()); push_varint(&mut out, group_id.len());
2922 out.extend_from_slice(group_id.as_bytes());
2923 out.extend_from_slice(&epoch.to_be_bytes());
2924 out.push(1); out.push(0); out.push(8); out.extend_from_slice(&[0u8; 8]);
2928 out.push(16); out.extend_from_slice(&[0u8; 16]);
2930 out
2931 }
2932
2933 #[test]
2934 fn test_forged_frame_reaches_session_desync_without_any_key_material() {
2935 let (_alice, bob) = create_test_session();
2948
2949 let forged = EncryptedMessage {
2950 group_id: slot("alice", "bob"),
2951 message_type: MlsMessageType::Application,
2952 epoch: 9_999,
2953 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
2954 sender_id: addr("alice"),
2955 timestamp_ms: 0,
2956 };
2957
2958 let err = bob.decrypt_from_user(&forged, &addr("alice")).unwrap_err();
2959 assert!(
2960 matches!(err, MlsError::SessionDesync(_)),
2961 "a forged future-epoch frame reaches the desync classification pre-AEAD, got {:?}",
2962 err
2963 );
2964 }
2965
2966 #[test]
2967 fn test_forged_frame_naming_a_foreign_slot_is_rejected_before_desync() {
2968 let (_alice, bob) = create_test_session();
2974
2975 let forged = EncryptedMessage {
2976 group_id: slot("alice", "bob"),
2977 message_type: MlsMessageType::Application,
2978 epoch: 9_999,
2979 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
2980 sender_id: addr("mallory"),
2981 timestamp_ms: 0,
2982 };
2983
2984 let err = bob
2985 .decrypt_from_user(&forged, &addr("mallory"))
2986 .unwrap_err();
2987 assert!(
2988 matches!(err, MlsError::SessionIdentityMismatch { .. }),
2989 "a frame naming another pair's slot must not classify as recoverable, got {:?}",
2990 err
2991 );
2992 }
2993
2994 #[test]
2995 fn test_corrupt_ciphertext_is_not_classified_as_session_desync() {
2996 let (alice, bob) = create_test_session();
3002 let mut ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
3003 for b in ct.ciphertext.iter_mut() {
3005 *b ^= 0xFF;
3006 }
3007 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
3008 assert!(
3009 !matches!(err, MlsError::SessionDesync(_)),
3010 "corrupt ciphertext must not be treated as a recoverable desync, got {:?}",
3011 err
3012 );
3013 }
3014
3015 #[test]
3016 fn test_join_session_binds_slot_to_inviter() {
3017 let bob = create_test_manager("bob");
3024
3025 let hijack = WelcomeMessage {
3028 group_id: slot("alice", "bob"),
3029 welcome_data: vec![], inviter_id: addr("mallory"),
3031 group_name: None,
3032 timestamp_ms: 0,
3033 };
3034
3035 let err = bob.join_session(&hijack).unwrap_err();
3037 assert!(
3038 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3039 "join_session: expected WelcomeIdentityMismatch, got {:?}",
3040 err
3041 );
3042 let err = bob.replace_session_with_welcome(&hijack).unwrap_err();
3043 assert!(
3044 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3045 "replace_session_with_welcome: expected WelcomeIdentityMismatch, got {:?}",
3046 err
3047 );
3048
3049 assert!(!bob.has_session(&addr("alice")).unwrap());
3051 assert!(!bob.has_session(&addr("mallory")).unwrap());
3052
3053 let bob_kp = bob.generate_key_package().unwrap();
3057 let mallory = create_test_manager("mallory");
3058 mallory
3059 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3060 .unwrap();
3061 let legit = mallory.create_session(&addr("bob")).unwrap();
3062 bob.join_session(&legit).unwrap();
3063 assert!(bob.has_session(&addr("mallory")).unwrap());
3064 }
3065
3066 #[test]
3067 fn test_join_group_rejects_session_namespace() {
3068 let bob = create_test_manager("bob");
3076
3077 let squat = WelcomeMessage {
3078 group_id: slot("alice", "bob"),
3079 welcome_data: vec![], inviter_id: addr("mallory"),
3081 group_name: None,
3082 timestamp_ms: 0,
3083 };
3084
3085 let err = bob.join_group(&squat).unwrap_err();
3086 assert!(
3087 matches!(err, MlsError::ReservedSessionNamespace { .. }),
3088 "join_group: expected ReservedSessionNamespace, got {:?}",
3089 err
3090 );
3091
3092 assert!(!bob.has_session(&addr("alice")).unwrap());
3094
3095 let legit_ns = WelcomeMessage {
3099 group_id: GroupId::new("group:0bd6e5f2-3a70-4a4e-9c3f-1c1f2a3b4c5d").unwrap(),
3100 welcome_data: vec![],
3101 inviter_id: addr("mallory"),
3102 group_name: None,
3103 timestamp_ms: 0,
3104 };
3105 let err = bob.join_group(&legit_ns).unwrap_err();
3106 assert!(
3107 !matches!(err, MlsError::ReservedSessionNamespace { .. }),
3108 "a group:-namespaced Welcome must not trip the session-namespace guard, got {:?}",
3109 err
3110 );
3111 }
3112
3113 #[test]
3114 fn test_join_group_rejects_embedded_group_id_mismatch() {
3115 let bob = create_test_manager("bob");
3124 let mallory = create_test_manager("mallory");
3125
3126 let bob_kp = bob.generate_key_package().unwrap();
3128 let embedded = GroupId::new("group:11111111-1111-4111-8111-111111111111").unwrap();
3129 let cred = mallory.get_credential().unwrap();
3130 let signer = mallory.get_signer().unwrap();
3131 mallory
3132 .group_manager
3133 .create_group(&embedded, &cred, &signer)
3134 .unwrap();
3135 let (welcome, _commit) = mallory
3136 .add_group_member(&embedded, &addr("bob"), &bob_kp.key_package_data)
3137 .unwrap();
3138 assert_eq!(welcome.group_id, embedded);
3139
3140 let wire = GroupId::new("group:22222222-2222-4222-8222-222222222222").unwrap();
3144 let tampered = WelcomeMessage {
3145 group_id: wire.clone(),
3146 ..welcome
3147 };
3148
3149 let err = bob.join_group(&tampered).unwrap_err();
3150 assert!(
3151 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3152 "expected WelcomeGroupIdMismatch, got {:?}",
3153 err
3154 );
3155
3156 assert!(bob.group_manager.load_group(&wire).unwrap().is_none());
3158 assert!(bob.group_manager.load_group(&embedded).unwrap().is_none());
3159 }
3160
3161 #[test]
3162 fn test_welcome_embedded_id_cannot_hijack_session_slot() {
3163 let alice = create_test_manager("alice");
3172 let bob = create_test_manager("bob");
3173 let bob_kp = bob.generate_key_package().unwrap();
3174 alice
3175 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3176 .unwrap();
3177 let welcome = alice.create_session(&addr("bob")).unwrap();
3178 bob.join_session(&welcome).unwrap();
3179 assert!(bob.has_session(&addr("alice")).unwrap());
3180
3181 let mallory = create_test_manager("mallory");
3183 let bob_kp2 = bob.generate_key_package().unwrap();
3184 let squat = slot("alice", "bob");
3185 let cred = mallory.get_credential().unwrap();
3186 let signer = mallory.get_signer().unwrap();
3187 mallory
3188 .group_manager
3189 .create_group(&squat, &cred, &signer)
3190 .unwrap();
3191 let (mal_welcome, _commit) = mallory
3192 .add_group_member(&squat, &addr("bob"), &bob_kp2.key_package_data)
3193 .unwrap();
3194
3195 let attack = WelcomeMessage {
3197 group_id: slot("bob", "mallory"),
3198 welcome_data: mal_welcome.welcome_data,
3199 inviter_id: addr("mallory"),
3200 group_name: None,
3201 timestamp_ms: 0,
3202 };
3203
3204 let err = bob.join_session(&attack).unwrap_err();
3205 assert!(
3206 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3207 "expected WelcomeGroupIdMismatch, got {:?}",
3208 err
3209 );
3210
3211 assert!(bob.has_session(&addr("alice")).unwrap());
3213 let ct = bob
3214 .encrypt_for_user(&addr("alice"), b"still private")
3215 .unwrap();
3216 let pt = alice.decrypt_from_user(&ct, &addr("bob")).unwrap();
3217 assert_eq!(pt.as_deref(), Some(&b"still private"[..]));
3218 }
3219
3220 fn create_test_group_with_bob() -> (MlsManager, MlsManager, GroupId) {
3223 let alice = create_test_manager("alice");
3224 let bob = create_test_manager("bob");
3225 let bob_kp = bob.generate_key_package().unwrap();
3226 alice
3227 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3228 .unwrap();
3229 let info = alice.create_group("Test Group").unwrap();
3230 let gid = info.group_id.clone();
3231 let (welcome, _commit) = alice
3232 .add_group_member(&gid, &addr("bob"), &bob_kp.key_package_data)
3233 .unwrap();
3234 bob.join_group(&welcome).unwrap();
3235 (alice, bob, gid)
3236 }
3237
3238 #[test]
3239 fn test_group_decrypt_rejects_spoofed_sender() {
3240 let (alice, bob, gid) = create_test_group_with_bob();
3241
3242 let ct = alice.encrypt_for_group(&gid, b"group message").unwrap();
3243 let err = bob.decrypt_from_group(&ct, &addr("mallory")).unwrap_err();
3244 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3245
3246 let ct2 = alice.encrypt_for_group(&gid, b"another one").unwrap();
3247 let pt = bob.decrypt_from_group(&ct2, &addr("alice")).unwrap();
3248 assert_eq!(pt.as_deref(), Some(&b"another one"[..]));
3249 }
3250
3251 #[test]
3252 fn test_group_commit_with_spoofed_sender_rejected_before_merge() {
3253 let (alice, bob, gid) = create_test_group_with_bob();
3254
3255 let commit = alice.update_keys(&gid).unwrap();
3259 let epoch_before = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3260
3261 let err = bob
3262 .decrypt_from_group(&commit, &addr("mallory"))
3263 .unwrap_err();
3264 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3265
3266 let epoch_after = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3267 assert_eq!(
3268 epoch_before, epoch_after,
3269 "spoofed commit must not advance group state"
3270 );
3271 }
3272
3273 #[test]
3274 fn test_no_session_initially() {
3275 let manager = create_test_manager("alice");
3276 assert!(!manager.has_session(&addr("bob")).unwrap());
3277 assert!(manager.list_sessions().unwrap().is_empty());
3278 }
3279
3280 #[test]
3281 fn test_no_groups_initially() {
3282 let manager = create_test_manager("alice");
3283 assert!(manager.list_groups().unwrap().is_empty());
3284 }
3285
3286 #[test]
3287 fn test_group_creation() {
3288 let manager = create_test_manager("alice");
3289 let info = manager.create_group("Test Group").unwrap();
3290
3291 assert_eq!(info.name, Some("Test Group".to_string()));
3292 assert!(!info.is_session);
3293 assert_eq!(info.members.len(), 1);
3294
3295 let groups = manager.list_groups().unwrap();
3296 assert_eq!(groups.len(), 1);
3297 }
3298
3299 #[test]
3310 fn test_both_create_adopt_is_non_destructive_on_welcome_retransmit() {
3311 let alice = create_test_manager("alice");
3313 let bob = create_test_manager("bob");
3314
3315 let alice_kp = alice.generate_key_package().unwrap();
3317 let bob_kp = bob.generate_key_package().unwrap();
3318 alice
3319 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3320 .unwrap();
3321 bob.import_key_package(&addr("alice"), &alice_kp.key_package_data)
3322 .unwrap();
3323
3324 let alice_welcome = alice.create_session(&addr("bob")).unwrap(); let _bob_welcome = bob.create_session(&addr("alice")).unwrap(); assert!(bob.has_session(&addr("alice")).unwrap());
3328
3329 bob.join_session(&alice_welcome).unwrap();
3332 assert!(bob.has_session(&addr("alice")).unwrap());
3333
3334 let ct = alice
3336 .encrypt_for_user(&addr("bob"), b"hello over the converged group")
3337 .unwrap();
3338 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
3339 assert_eq!(pt.as_deref(), Some(&b"hello over the converged group"[..]));
3340
3341 let retransmit = bob.join_session(&alice_welcome);
3344 assert!(
3345 retransmit.is_err(),
3346 "re-staging a consumed key package should fail"
3347 );
3348
3349 assert!(
3352 bob.has_session(&addr("alice")).unwrap(),
3353 "duplicate Welcome must not brick the converged session"
3354 );
3355
3356 let ct2 = alice
3358 .encrypt_for_user(&addr("bob"), b"still converged after retransmit")
3359 .unwrap();
3360 let pt2 = bob.decrypt_from_user(&ct2, &addr("alice")).unwrap();
3361 assert_eq!(
3362 pt2.as_deref(),
3363 Some(&b"still converged after retransmit"[..])
3364 );
3365 }
3366
3367 #[test]
3373 fn test_out_of_order_decryption_within_sender_ratchet_tolerance() {
3374 let alice = create_test_manager("alice");
3375 let bob = create_test_manager("bob");
3376
3377 let bob_kp = bob.generate_key_package().unwrap();
3378 alice
3379 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3380 .unwrap();
3381 let welcome = alice.create_session(&addr("bob")).unwrap();
3382 bob.join_session(&welcome).unwrap();
3383
3384 let ciphertexts: Vec<_> = (0..40)
3385 .map(|i| {
3386 alice
3387 .encrypt_for_user(&addr("bob"), format!("chunk {}", i).as_bytes())
3388 .unwrap()
3389 })
3390 .collect();
3391
3392 let pt = bob
3395 .decrypt_from_user(&ciphertexts[39], &addr("alice"))
3396 .unwrap();
3397 assert_eq!(pt.as_deref(), Some(&b"chunk 39"[..]));
3398
3399 let pt = bob
3402 .decrypt_from_user(&ciphertexts[10], &addr("alice"))
3403 .unwrap();
3404 assert_eq!(pt.as_deref(), Some(&b"chunk 10"[..]));
3405
3406 let res = bob.decrypt_from_user(&ciphertexts[0], &addr("alice"));
3409 assert!(
3410 !matches!(res, Ok(Some(_))),
3411 "generation beyond the tolerance must not decrypt"
3412 );
3413 }
3414
3415 const RFC8032_TV1_PK: [u8; 32] = [
3417 0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07,
3418 0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07,
3419 0x51, 0x1a,
3420 ];
3421
3422 const RFC8032_TV1_ADDRESS: &str = "off1qysluvwl5922yctzd0u9gpr06gn3k7ldfvgtwgvn";
3427
3428 #[test]
3429 fn derive_address_matches_the_pinned_vector() {
3430 let address = MlsManager::derive_address(&RFC8032_TV1_PK).expect("32-byte key derives");
3431 assert_eq!(address.to_string(), RFC8032_TV1_ADDRESS);
3432 assert_eq!(
3435 RFC8032_TV1_ADDRESS.parse::<Address>().expect("parses"),
3436 address
3437 );
3438 }
3439
3440 #[test]
3441 fn derive_address_rejects_wrong_key_lengths() {
3442 for len in [0usize, 31, 33, 64] {
3443 let key = vec![0u8; len];
3444 assert!(
3445 matches!(
3446 MlsManager::derive_address(&key),
3447 Err(MlsError::InvalidPublicKey(_))
3448 ),
3449 "{len}-byte key must be rejected"
3450 );
3451 }
3452 }
3453
3454 #[test]
3455 fn deprecated_derive_agrees_with_derive_address() {
3456 #[allow(deprecated)]
3459 let legacy = MlsManager::derive_user_id_from_public_key(&RFC8032_TV1_PK);
3460 assert_eq!(legacy, RFC8032_TV1_ADDRESS);
3461 assert_eq!(
3462 legacy,
3463 MlsManager::derive_address(&RFC8032_TV1_PK)
3464 .expect("derives")
3465 .to_string()
3466 );
3467 }
3468
3469 #[test]
3470 fn load_or_create_identity_mints_once_and_is_idempotent() {
3471 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3472
3473 let (first_keys, first_address) = MlsManager::load_or_create_identity(&storage).unwrap();
3474 let (second_keys, second_address) = MlsManager::load_or_create_identity(&storage).unwrap();
3475
3476 assert_eq!(first_keys.public(), second_keys.public());
3478 assert_eq!(first_address, second_address);
3479 assert_eq!(
3480 MlsManager::derive_address(first_keys.public()).unwrap(),
3481 first_address,
3482 "the returned address must be the stored key's derivation"
3483 );
3484 }
3485
3486 #[test]
3487 fn load_or_create_identity_adopts_an_existing_key() {
3488 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3489 let (keys, address) = test_identity("alice");
3490 seed_identity(&storage, &keys);
3491
3492 let (loaded, loaded_address) = MlsManager::load_or_create_identity(&storage).unwrap();
3493
3494 assert_eq!(loaded.public(), keys.public());
3495 assert_eq!(loaded_address, address);
3496 }
3497
3498 #[test]
3499 fn manager_bootstrapped_at_its_derived_address_loads_that_identity() {
3500 let (manager, address) = create_addressed_manager("alice");
3501
3502 assert_eq!(manager.user_id(), address.to_string());
3503 assert_eq!(
3504 MlsManager::derive_address(&manager.get_identity_public_key().unwrap()).unwrap(),
3505 address,
3506 "the manager's identity key must derive to the address it claims"
3507 );
3508 }
3509
3510 #[test]
3515 fn manager_refuses_an_address_whose_key_is_not_the_stored_one() {
3516 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3517 let (alice_keys, alice_address) = test_identity("alice");
3518 let (_, bob_address) = test_identity("bob");
3519 seed_identity(&storage, &alice_keys);
3520
3521 let Err(err) = MlsManager::new(bob_address.to_string(), storage) else {
3522 panic!("a manager must not claim an address it holds no key for");
3523 };
3524
3525 match err {
3526 MlsError::IdentityAddressMismatch { expected, derived } => {
3527 assert_eq!(expected, bob_address.to_string());
3528 assert_eq!(derived, alice_address.to_string());
3529 }
3530 other => panic!("expected IdentityAddressMismatch, got {other:?}"),
3531 }
3532 }
3533
3534 #[test]
3538 fn manager_refuses_to_claim_an_address_with_no_stored_identity() {
3539 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3540 let (_, address) = test_identity("alice");
3541
3542 let Err(err) = MlsManager::new(address.to_string(), storage.clone()) else {
3543 panic!("a manager must not mint a key and claim a different address");
3544 };
3545 assert!(matches!(err, MlsError::IdentityAddressMismatch { .. }));
3546
3547 assert!(
3548 storage
3549 .load(StorageKeyType::Identity.as_str(), "key_pair")
3550 .unwrap()
3551 .is_none(),
3552 "a refused bootstrap must not leave a key behind"
3553 );
3554 }
3555
3556 #[test]
3568 fn manager_with_a_non_address_id_skips_the_binding_check() {
3569 let manager = MlsManager::new("alice", Arc::new(InMemoryStorage::new())).unwrap();
3570 assert_eq!(manager.user_id(), "alice");
3571 }
3572
3573 struct Impostor {
3586 provider: MlsProvider,
3587 keys: SignatureKeyPair,
3588 key_package: Vec<u8>,
3589 group: Option<MlsGroup>,
3590 }
3591
3592 impl Impostor {
3593 fn claiming(claimed: &str, key_label: &str) -> Self {
3596 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3597 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
3598 let (keys, _) = test_identity(key_label);
3599 keys.store(provider.storage()).unwrap();
3600
3601 let credential = CredentialWithKey {
3602 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
3603 signature_key: keys.public().into(),
3604 };
3605 let key_package = KeyPackage::builder()
3606 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
3607 .unwrap()
3608 .key_package()
3609 .tls_serialize_detached()
3610 .unwrap();
3611
3612 Self {
3613 provider,
3614 keys,
3615 key_package,
3616 group: None,
3617 }
3618 }
3619
3620 fn join(&mut self, welcome: &MlsMessageOut) {
3622 let bytes = welcome.tls_serialize_detached().unwrap();
3623 let welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
3624 .unwrap()
3625 .extract()
3626 {
3627 MlsMessageBodyIn::Welcome(w) => w,
3628 _ => panic!("not a welcome"),
3629 };
3630 let cfg = MlsGroupJoinConfig::builder()
3631 .use_ratchet_tree_extension(true)
3632 .build();
3633 self.group = Some(
3634 StagedWelcome::new_from_welcome(&self.provider, &cfg, welcome, None)
3635 .unwrap()
3636 .into_group(&self.provider)
3637 .unwrap(),
3638 );
3639 }
3640
3641 fn merge_commit(&mut self, commit_bytes: &[u8]) {
3647 let group = self.group.as_mut().expect("join first");
3648 let message = MlsMessageIn::tls_deserialize_exact(commit_bytes)
3649 .unwrap()
3650 .try_into_protocol_message()
3651 .unwrap();
3652 let processed = group.process_message(&self.provider, message).unwrap();
3653 match processed.into_content() {
3654 ProcessedMessageContent::StagedCommitMessage(staged) => {
3655 group.merge_staged_commit(&self.provider, *staged).unwrap();
3656 }
3657 _ => panic!("not a commit"),
3658 }
3659 }
3660
3661 fn encrypt(&mut self, plaintext: &[u8]) -> Vec<u8> {
3663 self.group
3664 .as_mut()
3665 .expect("join first")
3666 .create_message(&self.provider, &self.keys, plaintext)
3667 .unwrap()
3668 .tls_serialize_detached()
3669 .unwrap()
3670 }
3671 }
3672
3673 fn add_member_bypassing_checks(
3681 manager: &MlsManager,
3682 group_id: &GroupId,
3683 key_package_bytes: &[u8],
3684 ) -> (MlsMessageOut, MlsMessageOut) {
3685 let mut group = manager
3686 .group_manager
3687 .load_group(group_id)
3688 .unwrap()
3689 .expect("group exists");
3690 let key_package = KeyPackageIn::tls_deserialize_exact(key_package_bytes)
3691 .unwrap()
3692 .validate(
3693 manager.group_manager.provider().crypto(),
3694 ProtocolVersion::Mls10,
3695 )
3696 .unwrap();
3697 let signer = manager.get_signer().unwrap();
3698 let (commit, welcome) = manager
3699 .group_manager
3700 .add_member(&mut group, key_package, &signer)
3701 .unwrap();
3702 manager.group_manager.save_group(group_id, &group).unwrap();
3703 (commit, welcome)
3704 }
3705
3706 fn commit_message(
3707 group_id: &GroupId,
3708 commit: &MlsMessageOut,
3709 sender: &str,
3710 ) -> EncryptedMessage {
3711 EncryptedMessage {
3712 group_id: group_id.clone(),
3713 message_type: MlsMessageType::Commit,
3714 epoch: 0,
3715 ciphertext: commit.tls_serialize_detached().unwrap(),
3716 sender_id: sender.to_string(),
3717 timestamp_ms: 0,
3718 }
3719 }
3720
3721 fn app_message(group_id: &GroupId, ciphertext: Vec<u8>, sender: &str) -> EncryptedMessage {
3722 EncryptedMessage {
3723 group_id: group_id.clone(),
3724 message_type: MlsMessageType::Application,
3725 epoch: 0,
3726 ciphertext,
3727 sender_id: sender.to_string(),
3728 timestamp_ms: 0,
3729 }
3730 }
3731
3732 #[test]
3740 fn test_committed_forged_leaf_is_refused_and_cannot_speak_as_its_victim() {
3741 let mallory = create_test_manager("mallory");
3742 let alice = create_test_manager("alice");
3743 let alice_kp = alice.generate_key_package().unwrap();
3744
3745 let gid = mallory.create_group("Team").unwrap().group_id;
3746 let (welcome, _) = mallory
3747 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
3748 .unwrap();
3749 alice.join_group(&welcome).unwrap();
3750 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
3751
3752 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
3754 assert!(matches!(
3755 mallory
3756 .add_group_member(&gid, &addr("bob"), &impostor.key_package)
3757 .unwrap_err(),
3758 MlsError::KeyPackageAddressMismatch { .. }
3759 ));
3760 let (commit, forged_welcome) =
3761 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
3762
3763 let err = alice
3765 .decrypt_from_group(
3766 &commit_message(&gid, &commit, &addr("mallory")),
3767 &addr("mallory"),
3768 )
3769 .unwrap_err();
3770 assert!(
3771 matches!(
3772 err,
3773 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
3774 if *claimed == addr("bob") && *context == crate::error::LeafSource::CommitAdd
3775 ),
3776 "expected a CommitAdd binding refusal, got {err:?}"
3777 );
3778
3779 let after = alice.get_group_info(&gid).unwrap().unwrap();
3781 assert_eq!(
3782 after.epoch, epoch_before,
3783 "a refused commit advanced the epoch"
3784 );
3785 assert!(
3786 !after.members.contains(&addr("bob")),
3787 "roster: {:?}",
3788 after.members
3789 );
3790
3791 impostor.join(&forged_welcome);
3799 let forged = impostor.encrypt(b"send me the recovery phrase");
3800 assert!(
3801 alice
3802 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
3803 .is_err(),
3804 "a forged leaf's message was delivered"
3805 );
3806 }
3807
3808 #[test]
3812 fn test_welcome_carrying_a_forged_leaf_is_refused() {
3813 let mallory = create_test_manager("mallory");
3814 let alice = create_test_manager("alice");
3815
3816 let gid = mallory.create_group("Team").unwrap().group_id;
3817 let impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
3818 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
3819
3820 let alice_kp = alice.generate_key_package().unwrap();
3822 let (welcome, _) = mallory
3823 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
3824 .unwrap();
3825
3826 let err = alice.join_group(&welcome).unwrap_err();
3827 assert!(
3828 matches!(
3829 err,
3830 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
3831 if *claimed == addr("bob") && *context == crate::error::LeafSource::WelcomeTree
3832 ),
3833 "expected a WelcomeTree binding refusal, got {err:?}"
3834 );
3835
3836 assert!(alice.get_group_info(&gid).unwrap().is_none());
3838 }
3839
3840 #[test]
3845 fn test_session_welcome_carrying_a_forged_leaf_is_refused_non_destructively() {
3846 let alice = create_test_manager("alice");
3847 let bob = create_test_manager("bob");
3848 let mallory = create_test_manager("mallory");
3849
3850 let bob_kp = bob.generate_key_package().unwrap();
3852 alice
3853 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3854 .unwrap();
3855 bob.join_session(&alice.create_session(&addr("bob")).unwrap())
3856 .unwrap();
3857 assert!(bob.has_session(&addr("alice")).unwrap());
3858
3859 let bob_kp2 = bob.generate_key_package().unwrap();
3862 let slot_id = slot("bob", "mallory");
3863 let cred = mallory.get_credential().unwrap();
3864 let signer = mallory.get_signer().unwrap();
3865 mallory
3866 .group_manager
3867 .create_group(&slot_id, &cred, &signer)
3868 .unwrap();
3869 let impostor = Impostor::claiming(&addr("carol"), "mallorys-second-key");
3870 add_member_bypassing_checks(&mallory, &slot_id, &impostor.key_package);
3871 let (welcome, _) = mallory
3872 .add_group_member(&slot_id, &addr("bob"), &bob_kp2.key_package_data)
3873 .unwrap();
3874
3875 let err = bob.join_session(&welcome).unwrap_err();
3876 assert!(
3877 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
3878 if *context == crate::error::LeafSource::WelcomeTree),
3879 "expected a WelcomeTree binding refusal, got {err:?}"
3880 );
3881
3882 assert!(bob.has_session(&addr("alice")).unwrap());
3884 let ct = bob
3885 .encrypt_for_user(&addr("alice"), b"still private")
3886 .unwrap();
3887 assert_eq!(
3888 alice
3889 .decrypt_from_user(&ct, &addr("bob"))
3890 .unwrap()
3891 .as_deref(),
3892 Some(&b"still private"[..])
3893 );
3894 }
3895
3896 #[test]
3904 fn test_forged_leaf_seated_behind_the_gates_still_cannot_speak() {
3905 let mallory = create_test_manager("mallory");
3906 let alice = create_test_manager("alice");
3907
3908 let gid = mallory.create_group("Team").unwrap().group_id;
3909 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
3910 let (_, forged_welcome) =
3911 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
3912 impostor.join(&forged_welcome);
3913
3914 let alice_kp = alice.generate_key_package().unwrap();
3915 let (welcome, add_alice_commit) = mallory
3916 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
3917 .unwrap();
3918 impostor.merge_commit(&add_alice_commit.ciphertext);
3921
3922 let bytes = welcome.welcome_data.clone();
3925 let staged_welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
3926 .unwrap()
3927 .extract()
3928 {
3929 MlsMessageBodyIn::Welcome(w) => w,
3930 _ => panic!("not a welcome"),
3931 };
3932 let cfg = MlsGroupJoinConfig::builder()
3933 .use_ratchet_tree_extension(true)
3934 .build();
3935 let group = StagedWelcome::new_from_welcome(&alice.provider, &cfg, staged_welcome, None)
3936 .unwrap()
3937 .into_group(&alice.provider)
3938 .unwrap();
3939 alice.group_manager.save_group(&gid, &group).unwrap();
3940
3941 assert!(
3943 group
3944 .members()
3945 .any(|m| m.credential.serialized_content() == addr("bob").as_bytes()),
3946 "test premise: the forged leaf must be installed"
3947 );
3948
3949 let forged = impostor.encrypt(b"send me the recovery phrase");
3951 let err = alice
3952 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
3953 .unwrap_err();
3954 assert!(
3955 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
3956 if *context == crate::error::LeafSource::MessageSender),
3957 "expected a MessageSender binding refusal, got {err:?}"
3958 );
3959
3960 let info = alice.get_group_info(&gid).unwrap().unwrap();
3963 assert!(
3964 !info.members.contains(&addr("bob")),
3965 "roster surfaced an unproven member: {:?}",
3966 info.members
3967 );
3968 assert!(
3969 info.members.contains(&addr("mallory")),
3970 "{:?}",
3971 info.members
3972 );
3973 }
3974
3975 #[test]
3989 fn test_member_renaming_their_own_leaf_to_a_peers_address_is_refused() {
3990 let (alice, bob, gid) = create_test_group_with_bob();
3994 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
3995
3996 let mut group = bob.group_manager.load_group(&gid).unwrap().unwrap();
3997 let signer = bob.get_signer().unwrap();
3998 let forged = CredentialWithKey {
3999 credential: Credential::new(CredentialType::Basic, addr("alice").as_bytes().to_vec()),
4002 signature_key: signer.public().into(),
4003 };
4004 let params = LeafNodeParameters::builder()
4005 .with_credential_with_key(forged)
4006 .build();
4007 let bundle = group
4008 .self_update(&bob.provider, &signer, params)
4009 .expect("MLS permits a credential change; the AS is what refuses it");
4010 group.merge_pending_commit(&bob.provider).unwrap();
4011 bob.group_manager.save_group(&gid, &group).unwrap();
4012 let (commit, _, _) = bundle.into_contents();
4013
4014 let err = alice
4015 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4016 .unwrap_err();
4017 assert!(
4018 matches!(
4019 err,
4020 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4021 if *claimed == addr("alice") && *context == crate::error::LeafSource::CommitPath
4022 ),
4023 "expected a CommitPath binding refusal, got {err:?}"
4024 );
4025
4026 let after = alice.get_group_info(&gid).unwrap().unwrap();
4029 assert_eq!(after.epoch, epoch_before);
4030 assert!(after.members.contains(&addr("bob")), "{:?}", after.members);
4031 assert_eq!(
4032 after
4033 .members
4034 .iter()
4035 .filter(|m| **m == addr("alice"))
4036 .count(),
4037 1,
4038 "alice must appear once, as herself: {:?}",
4039 after.members
4040 );
4041 }
4042
4043 #[test]
4046 fn test_leaf_with_a_nickname_credential_is_refused() {
4047 let mallory = create_test_manager("mallory");
4048 let alice = create_test_manager("alice");
4049 let alice_kp = alice.generate_key_package().unwrap();
4050
4051 let gid = mallory.create_group("Team").unwrap().group_id;
4052 let impostor = Impostor::claiming("bob", "mallorys-second-key");
4053 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4054 let (welcome, _) = mallory
4055 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4056 .unwrap();
4057
4058 let err = alice.join_group(&welcome).unwrap_err();
4059 assert!(
4060 matches!(err, MlsError::LeafAddressMismatch { ref claimed, .. } if claimed == "bob"),
4061 "a nickname credential was accepted or misreported: {err:?}"
4062 );
4063 }
4064
4065 #[test]
4073 fn test_key_rotation_and_ordinary_traffic_still_pass_the_binding() {
4074 let (alice, bob, gid) = create_test_group_with_bob();
4075
4076 let commit = alice.update_keys(&gid).unwrap();
4077 bob.decrypt_from_group(&commit, &addr("alice"))
4078 .expect("an honest key rotation must still merge");
4079
4080 let ct = alice.encrypt_for_group(&gid, b"after rotation").unwrap();
4081 assert_eq!(
4082 bob.decrypt_from_group(&ct, &addr("alice"))
4083 .unwrap()
4084 .as_deref(),
4085 Some(&b"after rotation"[..])
4086 );
4087
4088 assert!(bob
4090 .get_group_info(&gid)
4091 .unwrap()
4092 .unwrap()
4093 .members
4094 .contains(&addr("bob")));
4095 }
4096
4097 #[test]
4105 fn test_welcome_whose_group_context_has_external_senders_is_refused() {
4106 let mallory = create_test_manager("mallory");
4107 let alice = create_test_manager("alice");
4108
4109 let gid = mallory.create_group("Team").unwrap().group_id;
4110
4111 {
4113 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
4114 let signer = mallory.get_signer().unwrap();
4115 let outsider =
4116 SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
4117 let external = ExternalSender::new(
4118 outsider.public().into(),
4119 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
4120 );
4121 group
4122 .commit_builder()
4123 .propose_group_context_extensions(Extensions::single(Extension::ExternalSenders(
4124 vec![external],
4125 )))
4126 .load_psks(mallory.provider.storage())
4127 .unwrap()
4128 .build(
4129 mallory.provider.rand(),
4130 mallory.provider.crypto(),
4131 &signer,
4132 |_| true,
4133 )
4134 .unwrap()
4135 .stage_commit(&mallory.provider)
4136 .unwrap();
4137 group.merge_pending_commit(&mallory.provider).unwrap();
4138 mallory.group_manager.save_group(&gid, &group).unwrap();
4139 }
4140
4141 let alice_kp = alice.generate_key_package().unwrap();
4142 let (welcome, _) = mallory
4143 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4144 .unwrap();
4145
4146 let err = alice.join_group(&welcome).unwrap_err();
4147 assert!(
4148 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4149 if detail.contains("ExternalSenders")),
4150 "expected the invite to be declined for its ExternalSenders context, got {err:?}"
4151 );
4152 assert!(
4153 alice.get_group_info(&gid).unwrap().is_none(),
4154 "a declined Welcome must not install the group"
4155 );
4156 }
4157
4158 #[test]
4167 fn test_commit_proposing_external_senders_is_refused() {
4168 let (alice, mallory, gid) = create_test_group_with_bob();
4169
4170 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
4171 let signer = mallory.get_signer().unwrap();
4172
4173 let outsider = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
4175 let external = ExternalSender::new(
4176 outsider.public().into(),
4177 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
4178 );
4179 let extensions = Extensions::single(Extension::ExternalSenders(vec![external]));
4180
4181 let bundle = group
4182 .commit_builder()
4183 .propose_group_context_extensions(extensions)
4184 .load_psks(mallory.provider.storage())
4185 .unwrap()
4186 .build(
4187 mallory.provider.rand(),
4188 mallory.provider.crypto(),
4189 &signer,
4190 |_| true,
4191 )
4192 .unwrap()
4193 .stage_commit(&mallory.provider)
4194 .unwrap();
4195 let (commit, _, _) = bundle.into_contents();
4196
4197 let err = alice
4198 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4199 .unwrap_err();
4200 assert!(
4201 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4202 if detail.contains("ExternalSenders")),
4203 "expected an ExternalSenders refusal, got {err:?}"
4204 );
4205 }
4206
4207 #[test]
4217 fn test_one_identity_cannot_hold_two_leaves_in_a_group() {
4218 let alice = create_test_manager("alice");
4219 let bob = create_test_manager("bob");
4220
4221 let gid = alice.create_group("Team").unwrap().group_id;
4222 let first = bob.generate_key_package().unwrap();
4223 alice
4224 .add_group_member(&gid, &addr("bob"), &first.key_package_data)
4225 .unwrap();
4226
4227 let second = bob.generate_key_package().unwrap();
4230 assert_ne!(first.package_id, second.package_id);
4231 let err = alice
4232 .add_group_member(&gid, &addr("bob"), &second.key_package_data)
4233 .unwrap_err();
4234 assert!(
4235 matches!(err, MlsError::AddMember(ref m) if m.contains("Duplicate signature key")),
4236 "expected MLS to refuse a duplicate signature key, got {err:?}"
4237 );
4238
4239 alice.remove_group_member(&gid, &addr("bob")).unwrap();
4241 assert!(!alice
4242 .get_group_info(&gid)
4243 .unwrap()
4244 .unwrap()
4245 .members
4246 .contains(&addr("bob")));
4247 }
4248
4249 #[test]
4265 fn test_external_join_proposal_is_refused_as_an_unsupported_sender() {
4266 let (alice, _bob, gid) = create_test_group_with_bob();
4267
4268 let outsider = create_test_manager("mallory");
4272 let kp_bytes = outsider.generate_key_package().unwrap().key_package_data;
4273 let key_package = KeyPackageIn::tls_deserialize_exact(&kp_bytes)
4274 .unwrap()
4275 .validate(alice.provider.crypto(), ProtocolVersion::Mls10)
4276 .unwrap();
4277 let signer = outsider.get_signer().unwrap();
4278
4279 let (mls_group_id, epoch) = {
4280 let group = alice.group_manager.load_group(&gid).unwrap().unwrap();
4281 (group.group_id().clone(), group.epoch())
4282 };
4283 let proposal =
4284 JoinProposal::new::<MlsStorageAdapter>(key_package, mls_group_id, epoch, &signer)
4285 .expect(
4286 "an external join proposal is well-formed; the SDK just declines to honour it",
4287 );
4288
4289 let err = alice
4290 .decrypt_from_group(
4291 &app_message(
4292 &gid,
4293 proposal.tls_serialize_detached().unwrap(),
4294 &addr("mallory"),
4295 ),
4296 &addr("mallory"),
4297 )
4298 .unwrap_err();
4299 assert!(
4300 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4301 if detail.contains("not a group member")),
4302 "expected an UnsupportedSender refusal, got {err:?}"
4303 );
4304 }
4305
4306 #[test]
4320 fn test_roster_read_counts_the_leaves_it_skips() {
4321 let (alice, _bob, gid) = create_test_group_with_bob();
4322
4323 let before = alice.get_group_info(&gid).unwrap().unwrap();
4324 assert_eq!(
4325 before.unproven_members, 0,
4326 "an honest group must report no unproven members: {:?}",
4327 before.members
4328 );
4329 let honest_members = before.members.len();
4330
4331 let impostor = Impostor::claiming(&addr("carol"), "an-attackers-key");
4334 add_member_bypassing_checks(&alice, &gid, &impostor.key_package);
4335
4336 let after = alice.get_group_info(&gid).unwrap().unwrap();
4337 assert_eq!(
4338 after.unproven_members, 1,
4339 "the skipped leaf must be counted, not only dropped"
4340 );
4341 assert_eq!(
4342 after.members.len(),
4343 honest_members,
4344 "the roster must be unchanged: {:?}",
4345 after.members
4346 );
4347 assert!(
4348 !after.members.contains(&addr("carol")),
4349 "roster surfaced the unproven member: {:?}",
4350 after.members
4351 );
4352 }
4353}