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 offline_protocol_sealed::MAX_ACCEPTED_KEY_PACKAGE_LIFETIME;
16use openmls::prelude::tls_codec::{Deserialize as TlsDeserialize, Serialize as TlsSerialize};
17use openmls::prelude::*;
18use openmls_basic_credential::SignatureKeyPair;
19use openmls_traits::OpenMlsProvider;
20use std::sync::{Arc, RwLock};
21use tracing::{debug, info, warn};
22use uuid::Uuid;
23
24const DEFAULT_KEY_PACKAGE_LIFETIME_SECS: u64 = 30 * 24 * 60 * 60;
39
40const _: () = assert!(
44 DEFAULT_KEY_PACKAGE_LIFETIME_SECS + LIFETIME_SKEW_MARGIN_SECS
45 <= MAX_ACCEPTED_KEY_PACKAGE_LIFETIME.as_secs(),
46 "this install mints a key package it would refuse from a peer"
47);
48
49const LIFETIME_SKEW_MARGIN_SECS: u64 = 60 * 60;
57
58const KEY_PACKAGE_PURGE_GRACE_SECS: u64 = 7 * 24 * 60 * 60;
68
69pub const MAX_PUSH_KEY_PACKAGES: usize = 64;
80
81#[derive(Debug, Clone)]
88pub struct PushKeyPackage {
89 pub bundle: KeyPackageBundle,
91 pub pool_exhausted: bool,
93}
94
95fn newer_than(candidate: &KeyPackageBundle, current: Option<&KeyPackageBundle>) -> bool {
98 current.is_none_or(|existing| candidate.created_at_ms > existing.created_at_ms)
99}
100
101pub struct MlsManager {
103 user_id: String,
105
106 storage: Arc<dyn MlsStorage>,
108
109 provider: MlsProvider,
111
112 credential: RwLock<Option<CredentialWithKey>>,
114
115 cached_signer: RwLock<Option<SignatureKeyPair>>,
117
118 session_manager: SessionManager,
120
121 group_manager: GroupManager,
123}
124
125impl MlsManager {
126 pub fn new(user_id: impl Into<String>, storage: Arc<dyn MlsStorage>) -> Result<Self> {
128 let user_id = user_id.into();
129
130 let adapter = MlsStorageAdapter::new(storage.clone());
131 let provider = MlsProvider::new(adapter);
132
133 let session_manager =
134 SessionManager::new(user_id.clone(), storage.clone(), provider.clone());
135 let group_manager = GroupManager::new(storage.clone(), provider.clone());
136
137 let manager = Self {
138 user_id,
139 storage,
140 provider,
141 credential: RwLock::new(None),
142 cached_signer: RwLock::new(None),
143 session_manager,
144 group_manager,
145 };
146
147 manager.ensure_identity()?;
148
149 Ok(manager)
150 }
151
152 pub fn user_id(&self) -> &str {
154 &self.user_id
155 }
156
157 pub fn load_or_create_identity(
173 storage: &Arc<dyn MlsStorage>,
174 ) -> Result<(SignatureKeyPair, Address)> {
175 let key_type = StorageKeyType::Identity.as_str();
176
177 let signature_keys = match storage.load(key_type, "key_pair")? {
178 Some(json) => serde_json::from_slice(&json).map_err(|e| {
179 MlsError::Deserialization(format!("Failed to deserialize signature keys: {}", e))
180 })?,
181 None => {
182 let keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
183 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
184 let keys_json = serde_json::to_vec(&keys)
185 .map_err(|e| MlsError::Serialization(e.to_string()))?;
186 storage.store(key_type, "key_pair", &keys_json)?;
187 info!("Minted a new identity key");
188 keys
189 }
190 };
191
192 let address = Self::derive_address(signature_keys.public())?;
193 Ok((signature_keys, address))
194 }
195
196 fn ensure_identity(&self) -> Result<()> {
198 if self.load_identity()? {
199 return Ok(());
200 }
201 self.create_identity()?;
202 info!(user_id = %self.user_id, "Created new MLS identity");
203 Ok(())
204 }
205
206 fn verify_identity_binding(&self, public_key: &[u8]) -> Result<()> {
213 let Ok(expected) = self.user_id.parse::<Address>() else {
214 return Ok(());
215 };
216
217 let derived = Self::derive_address(public_key)?;
218 if derived != expected {
219 return Err(MlsError::IdentityAddressMismatch {
220 expected: expected.to_string(),
221 derived: derived.to_string(),
222 });
223 }
224 Ok(())
225 }
226
227 fn load_identity(&self) -> Result<bool> {
229 let key_type = StorageKeyType::Identity.as_str();
230 let keys_data = self.storage.load(key_type, "key_pair")?;
231
232 match keys_data {
233 Some(json) => {
234 let signature_keys: SignatureKeyPair =
235 serde_json::from_slice(&json).map_err(|e| {
236 MlsError::Deserialization(format!(
237 "Failed to deserialize signature keys: {}",
238 e
239 ))
240 })?;
241
242 let public_key = signature_keys.public();
243
244 self.verify_identity_binding(public_key)?;
249
250 let credential =
251 Credential::new(CredentialType::Basic, self.user_id.as_bytes().to_vec());
252
253 let credential_with_key = CredentialWithKey {
254 credential,
255 signature_key: public_key.into(),
256 };
257
258 {
259 let mut guard = self
260 .credential
261 .write()
262 .map_err(|_| MlsError::NotInitialized)?;
263 *guard = Some(credential_with_key);
264 }
265 {
266 let mut guard = self
267 .cached_signer
268 .write()
269 .map_err(|_| MlsError::NotInitialized)?;
270 *guard = Some(signature_keys);
271 }
272
273 debug!(user_id = %self.user_id, "Loaded existing MLS identity");
274 Ok(true)
275 }
276 None => Ok(false),
277 }
278 }
279
280 fn create_identity(&self) -> Result<()> {
282 let signature_keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
283 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
284
285 self.verify_identity_binding(signature_keys.public())?;
293
294 let keys_json = serde_json::to_vec(&signature_keys)
295 .map_err(|e| MlsError::Serialization(e.to_string()))?;
296
297 let key_type = StorageKeyType::Identity.as_str();
298 self.storage.store(key_type, "key_pair", &keys_json)?;
299
300 let public_key = signature_keys.public();
301
302 let credential = Credential::new(CredentialType::Basic, self.user_id.as_bytes().to_vec());
303
304 let credential_with_key = CredentialWithKey {
305 credential,
306 signature_key: public_key.into(),
307 };
308
309 {
310 let mut guard = self
311 .credential
312 .write()
313 .map_err(|_| MlsError::NotInitialized)?;
314 *guard = Some(credential_with_key);
315 }
316 {
317 let mut guard = self
318 .cached_signer
319 .write()
320 .map_err(|_| MlsError::NotInitialized)?;
321 *guard = Some(signature_keys);
322 }
323
324 Ok(())
325 }
326
327 fn get_credential(&self) -> Result<CredentialWithKey> {
329 let guard = self
330 .credential
331 .read()
332 .map_err(|_| MlsError::NotInitialized)?;
333 guard.clone().ok_or(MlsError::NotInitialized)
334 }
335
336 fn get_signer(&self) -> Result<SignatureKeyPair> {
341 let guard = self
342 .cached_signer
343 .read()
344 .map_err(|_| MlsError::NotInitialized)?;
345
346 let cached = guard.as_ref().ok_or(MlsError::NotInitialized)?;
347 let bytes =
348 serde_json::to_vec(cached).map_err(|e| MlsError::Serialization(e.to_string()))?;
349 serde_json::from_slice(&bytes).map_err(|e| MlsError::Deserialization(e.to_string()))
350 }
351
352 pub fn generate_key_package(&self) -> Result<KeyPackageBundle> {
358 self.generate_key_package_inner(false)
359 }
360
361 pub fn generate_publication_key_package(&self) -> Result<KeyPackageBundle> {
368 self.generate_key_package_inner(true)
369 }
370
371 fn generate_key_package_inner(&self, reserved: bool) -> Result<KeyPackageBundle> {
372 let credential = self.get_credential()?;
373 let signature_keys = self.get_signer()?;
374
375 let key_package_bundle = KeyPackage::builder()
380 .key_package_lifetime(Lifetime::new(DEFAULT_KEY_PACKAGE_LIFETIME_SECS))
381 .build(
382 DEFAULT_CIPHERSUITE,
383 &self.provider,
384 &signature_keys,
385 credential,
386 )
387 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?;
388
389 let key_package_data = key_package_bundle
390 .key_package()
391 .tls_serialize_detached()
392 .map_err(|e| MlsError::Serialization(e.to_string()))?;
393
394 let provider_hash_ref = key_package_bundle
399 .key_package()
400 .hash_ref(self.provider.crypto())
401 .ok()
402 .and_then(|hash_ref| hash_ref.tls_serialize_detached().ok());
403
404 let package_id = Uuid::new_v4().to_string();
405
406 let key_type = StorageKeyType::KeyPackage.as_str();
407 let mut bundle = KeyPackageBundle::new(
408 package_id,
409 self.user_id.clone(),
410 key_package_data,
411 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
412 );
413 bundle.reserved_for_publication = reserved;
414 bundle.provider_hash_ref = provider_hash_ref;
415 let serialized =
416 serde_json::to_vec(&bundle).map_err(|e| MlsError::Serialization(e.to_string()))?;
417 self.storage
418 .store(key_type, &bundle.package_id, &serialized)?;
419
420 debug!(package_id = %bundle.package_id, "Generated new key package");
421 Ok(bundle)
422 }
423
424 pub fn get_or_create_key_package(&self) -> Result<KeyPackageBundle> {
442 let key_type = StorageKeyType::KeyPackage.as_str();
443 let packages = self.storage.list_keys(key_type)?;
444
445 for package_id in packages {
446 if let Some(bundle) = self.load_stored_key_package(&package_id)? {
447 if bundle.reserved_for_publication || bundle.assigned_peer.is_some() {
448 continue;
449 }
450 return Ok(bundle);
451 }
452 }
453
454 self.generate_key_package()
455 }
456
457 pub fn take_push_key_package(&self, peer_id: &str) -> Result<PushKeyPackage> {
505 let key_type = StorageKeyType::KeyPackage.as_str();
506 let package_ids = self.storage.list_keys(key_type)?;
507
508 let mut live = 0usize;
509 let mut assigned_here: Option<KeyPackageBundle> = None;
510 let mut unclaimed: Option<KeyPackageBundle> = None;
511 let mut newest: Option<KeyPackageBundle> = None;
512
513 for package_id in package_ids {
517 let Some(bundle) = self.load_stored_key_package(&package_id)? else {
518 continue;
519 };
520 if bundle.reserved_for_publication {
521 continue;
522 }
523 live += 1;
524
525 if bundle.assigned_peer.as_deref() == Some(peer_id) {
526 if newer_than(&bundle, assigned_here.as_ref()) {
528 assigned_here = Some(bundle.clone());
529 }
530 } else if bundle.assigned_peer.is_none() && newer_than(&bundle, unclaimed.as_ref()) {
531 unclaimed = Some(bundle.clone());
532 }
533
534 if newer_than(&bundle, newest.as_ref()) {
535 newest = Some(bundle);
536 }
537 }
538
539 if let Some(bundle) = assigned_here {
540 return Ok(PushKeyPackage {
541 bundle,
542 pool_exhausted: false,
543 });
544 }
545
546 if let Some(bundle) = unclaimed {
549 return Ok(PushKeyPackage {
550 bundle: self.claim_key_package(bundle, peer_id)?,
551 pool_exhausted: false,
552 });
553 }
554
555 if live >= MAX_PUSH_KEY_PACKAGES {
558 if let Some(bundle) = newest {
561 debug!(
562 peer_id = %peer_id,
563 live = live,
564 "Push key-package pool at capacity; reusing an existing package"
565 );
566 return Ok(PushKeyPackage {
567 bundle,
568 pool_exhausted: true,
569 });
570 }
571 }
572
573 let bundle = self.claim_key_package(self.generate_key_package()?, peer_id)?;
574
575 Ok(PushKeyPackage {
576 bundle,
577 pool_exhausted: false,
578 })
579 }
580
581 fn claim_key_package(
583 &self,
584 mut bundle: KeyPackageBundle,
585 peer_id: &str,
586 ) -> Result<KeyPackageBundle> {
587 bundle.assigned_peer = Some(peer_id.to_string());
588 let serialized =
589 serde_json::to_vec(&bundle).map_err(|e| MlsError::Serialization(e.to_string()))?;
590 self.storage.store(
591 StorageKeyType::KeyPackage.as_str(),
592 &bundle.package_id,
593 &serialized,
594 )?;
595 debug!(
596 package_id = %bundle.package_id,
597 peer_id = %peer_id,
598 "Assigned key package to peer"
599 );
600 Ok(bundle)
601 }
602
603 pub fn key_package_by_id(&self, package_id: &str) -> Result<Option<KeyPackageBundle>> {
611 self.load_stored_key_package(package_id)
612 }
613
614 pub fn delete_key_package(&self, package_id: &str) -> Result<()> {
627 let Some(data) = self
628 .storage
629 .load(StorageKeyType::KeyPackage.as_str(), package_id)?
630 else {
631 return Ok(());
632 };
633
634 match serde_json::from_slice::<KeyPackageBundle>(&data) {
635 Ok(bundle) => self.purge_key_package_material(package_id, &bundle),
636 Err(_) => {
644 let legacy = KeyPackageBundle::new(
645 package_id.to_string(),
646 self.user_id.clone(),
647 data,
648 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
649 );
650 self.purge_key_package_material(package_id, &legacy)
651 }
652 }
653 }
654
655 pub fn import_key_package(&self, user_id: &str, key_package_data: &[u8]) -> Result<()> {
670 offline_protocol_core::validate_id_chars(user_id, "User ID")
671 .map_err(|e| MlsError::InvalidUserId(e.to_string()))?;
672
673 let key_package_in = KeyPackageIn::tls_deserialize_exact(key_package_data)
674 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
675 let key_package = key_package_in
676 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
677 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
678
679 Self::verify_credential_identity(&key_package, user_id)?;
680 Self::verify_address_binding(&key_package, user_id)?;
681 Self::verify_lifetime_bound(&key_package)?;
682
683 let key_type = StorageKeyType::ContactKeyPackage.as_str();
684 self.storage.store(key_type, user_id, key_package_data)?;
685
686 debug!(user_id = %user_id, "Imported key package for contact");
687 Ok(())
688 }
689
690 fn get_contact_key_package(&self, user_id: &str) -> Result<KeyPackage> {
692 let key_type = StorageKeyType::ContactKeyPackage.as_str();
693 let data = self
694 .storage
695 .load(key_type, user_id)?
696 .ok_or_else(|| MlsError::NoKeyPackage(user_id.to_string()))?;
697
698 let key_package_in = KeyPackageIn::tls_deserialize_exact(&data)
699 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
700
701 let key_package = key_package_in
703 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
704 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
705
706 Self::verify_credential_identity(&key_package, user_id)?;
712 Self::verify_address_binding(&key_package, user_id)?;
713 Self::verify_lifetime_bound(&key_package)?;
714
715 Ok(key_package)
716 }
717
718 fn verify_lifetime_bound(key_package: &KeyPackage) -> Result<()> {
749 let lifetime = key_package.life_time();
750 let width = lifetime.not_after().saturating_sub(lifetime.not_before());
751 let cap = MAX_ACCEPTED_KEY_PACKAGE_LIFETIME.as_secs();
752
753 if width > cap {
754 return Err(MlsError::InvalidKeyPackage(format!(
755 "key package validity window is {} seconds, wider than the {} \
756 seconds this install admits",
757 width, cap
758 )));
759 }
760 Ok(())
761 }
762
763 fn verify_address_binding(key_package: &KeyPackage, user_id: &str) -> Result<()> {
792 let claimed = user_id.parse::<Address>().map_err(|e| {
793 MlsError::InvalidUserId(format!("'{}' is not an address: {}", user_id, e))
794 })?;
795
796 let derived = Self::derive_address(key_package.leaf_node().signature_key().as_slice())?;
797 if derived != claimed {
798 return Err(MlsError::KeyPackageAddressMismatch {
799 claimed: claimed.to_string(),
800 derived: derived.to_string(),
801 });
802 }
803 Ok(())
804 }
805
806 fn verify_credential_identity(key_package: &KeyPackage, user_id: &str) -> Result<()> {
810 let identity = key_package.leaf_node().credential().serialized_content();
811 if identity != user_id.as_bytes() {
812 return Err(MlsError::CredentialIdentityMismatch {
813 expected: user_id.to_string(),
814 found: String::from_utf8_lossy(identity).into_owned(),
815 });
816 }
817 Ok(())
818 }
819
820 pub fn get_pending_key_packages(&self) -> Result<Vec<KeyPackageBundle>> {
822 let key_type = StorageKeyType::KeyPackage.as_str();
823 let package_ids = self.storage.list_keys(key_type)?;
824
825 let mut bundles = Vec::new();
826 for package_id in package_ids {
827 if let Some(bundle) = self.load_stored_key_package(&package_id)? {
828 bundles.push(bundle);
829 }
830 }
831
832 Ok(bundles)
833 }
834
835 pub fn mark_key_package_synced(&self, package_id: &str) -> Result<()> {
837 let key_type = StorageKeyType::KeyPackage.as_str();
838 self.storage.delete(key_type, package_id)?;
839 debug!(package_id = %package_id, "Marked key package as synced");
840 Ok(())
841 }
842
843 pub fn has_session(&self, other_user_id: &str) -> Result<bool> {
849 self.session_manager.has_session(other_user_id)
850 }
851
852 pub fn create_session(&self, other_user_id: &str) -> Result<WelcomeMessage> {
854 let their_key_package = self.get_contact_key_package(other_user_id)?;
855 let credential = self.get_credential()?;
856 let signature_keys = self.get_signer()?;
857
858 let welcome = self.session_manager.create_session(
859 other_user_id,
860 their_key_package,
861 &credential,
862 &signature_keys,
863 )?;
864
865 let key_type = StorageKeyType::ContactKeyPackage.as_str();
866 self.storage.delete(key_type, other_user_id)?;
867
868 Ok(welcome)
869 }
870
871 pub fn join_session(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
873 self.session_manager.join_session(welcome)
874 }
875
876 pub fn replace_session_with_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
883 let other_user_id = &welcome.inviter_id;
884
885 offline_protocol_core::validate_id_chars(other_user_id, "User ID")
889 .map_err(|e| MlsError::InvalidUserId(e.to_string()))?;
890
891 self.session_manager.verify_welcome_slot(welcome)?;
896
897 let _ = self.clear_pending_welcome(other_user_id);
899
900 let key_type = StorageKeyType::ContactKeyPackage.as_str();
902 let _ = self.storage.delete(key_type, other_user_id);
903
904 self.session_manager.join_session(welcome)
908 }
909
910 pub fn encrypt_for_user(
912 &self,
913 other_user_id: &str,
914 plaintext: &[u8],
915 ) -> Result<EncryptedMessage> {
916 if !self.has_session(other_user_id)? {
917 let welcome = self.create_session(other_user_id)?;
918 warn!(
919 other_user_id = %other_user_id,
920 "Created new session - Welcome message needs to be sent"
921 );
922 let key_type = StorageKeyType::PendingWelcome.as_str();
923 let welcome_data =
924 serde_json::to_vec(&welcome).map_err(|e| MlsError::Serialization(e.to_string()))?;
925 self.storage.store(key_type, other_user_id, &welcome_data)?;
926 }
927
928 let signature_keys = self.get_signer()?;
929 self.session_manager
930 .encrypt_message(other_user_id, plaintext, &signature_keys)
931 }
932
933 pub fn encrypt_for_existing_session(
940 &self,
941 other_user_id: &str,
942 plaintext: &[u8],
943 ) -> Result<EncryptedMessage> {
944 let signature_keys = self.get_signer()?;
945 self.session_manager
946 .encrypt_message(other_user_id, plaintext, &signature_keys)
947 }
948
949 pub fn get_pending_welcome(&self, other_user_id: &str) -> Result<Option<WelcomeMessage>> {
951 let key_type = StorageKeyType::PendingWelcome.as_str();
952 match self.storage.load(key_type, other_user_id)? {
953 Some(data) => {
954 let welcome: WelcomeMessage = serde_json::from_slice(&data)
955 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
956 Ok(Some(welcome))
957 }
958 None => Ok(None),
959 }
960 }
961
962 pub fn clear_pending_welcome(&self, other_user_id: &str) -> Result<()> {
964 let key_type = StorageKeyType::PendingWelcome.as_str();
965 self.storage.delete(key_type, other_user_id)?;
966 Ok(())
967 }
968
969 pub fn decrypt_from_user(
976 &self,
977 encrypted: &EncryptedMessage,
978 claimed_sender: &str,
979 ) -> Result<Option<Vec<u8>>> {
980 self.session_manager
981 .decrypt_message(encrypted, claimed_sender)
982 }
983
984 pub fn list_sessions(&self) -> Result<Vec<String>> {
986 self.session_manager.list_sessions()
987 }
988
989 pub fn delete_session(&self, other_user_id: &str) -> Result<()> {
991 self.session_manager.delete_session(other_user_id)
992 }
993
994 pub fn create_group(&self, group_name: &str) -> Result<GroupInfo> {
1000 let group_id = GroupId::new(format!("group:{}", Uuid::new_v4()))?;
1001 let credential = self.get_credential()?;
1002 let signature_keys = self.get_signer()?;
1003
1004 let group = self
1005 .group_manager
1006 .create_group(&group_id, &credential, &signature_keys)?;
1007
1008 let metadata = GroupMetadata::new_with_creator(Some(group_name.to_string()), &self.user_id);
1010 self.save_group_metadata(&group_id, &metadata)?;
1011
1012 let mut info = self.group_manager.get_group_info(&group, &group_id);
1013 info.name = metadata.name;
1014 info.created_at_ms = metadata.created_at_ms;
1015 info.last_activity_ms = metadata.last_activity_ms;
1016
1017 info!(group_id = %group_id, name = %group_name, "Created new group");
1018 Ok(info)
1019 }
1020
1021 pub fn add_group_member(
1043 &self,
1044 group_id: &GroupId,
1045 invitee_user_id: &str,
1046 member_key_package: &[u8],
1047 ) -> Result<(WelcomeMessage, EncryptedMessage)> {
1048 let key_package = KeyPackageIn::tls_deserialize_exact(member_key_package)
1049 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?
1050 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
1051 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
1052
1053 Self::verify_credential_identity(&key_package, invitee_user_id)?;
1054 Self::verify_address_binding(&key_package, invitee_user_id)?;
1055 Self::verify_lifetime_bound(&key_package)?;
1056
1057 let mut group = self
1058 .group_manager
1059 .load_group(group_id)?
1060 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1061
1062 let signature_keys = self.get_signer()?;
1063 let (commit, welcome) =
1064 self.group_manager
1065 .add_member(&mut group, key_package, &signature_keys)?;
1066
1067 self.group_manager.save_group(group_id, &group)?;
1068
1069 let welcome_bytes = welcome
1070 .tls_serialize_detached()
1071 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1072
1073 let commit_bytes = commit
1074 .tls_serialize_detached()
1075 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1076
1077 let group_name = self.load_group_metadata(group_id)?.and_then(|m| m.name);
1079
1080 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
1081
1082 let welcome_msg = WelcomeMessage {
1083 group_id: group_id.clone(),
1084 welcome_data: welcome_bytes,
1085 inviter_id: self.user_id.clone(),
1086 group_name,
1087 timestamp_ms: now_ms,
1088 };
1089
1090 let commit_msg = EncryptedMessage {
1091 group_id: group_id.clone(),
1092 message_type: MlsMessageType::Commit,
1093 epoch: group.epoch().as_u64(),
1094 ciphertext: commit_bytes,
1095 sender_id: self.user_id.clone(),
1096 timestamp_ms: now_ms,
1097 };
1098
1099 Ok((welcome_msg, commit_msg))
1100 }
1101
1102 pub fn remove_group_member(
1129 &self,
1130 group_id: &GroupId,
1131 member_id: &str,
1132 ) -> Result<EncryptedMessage> {
1133 let mut group = self
1134 .group_manager
1135 .load_group(group_id)?
1136 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1137
1138 let member_indices: Vec<LeafNodeIndex> = group
1139 .members()
1140 .filter(|m| m.credential.serialized_content() == member_id.as_bytes())
1141 .map(|m| m.index)
1142 .collect();
1143
1144 if member_indices.is_empty() {
1145 return Err(MlsError::UserNotInGroup(member_id.to_string()));
1146 }
1147
1148 let signature_keys = self.get_signer()?;
1149 let commit =
1150 self.group_manager
1151 .remove_member(&mut group, &member_indices, &signature_keys)?;
1152
1153 self.group_manager.save_group(group_id, &group)?;
1154
1155 let ciphertext = commit
1156 .tls_serialize_detached()
1157 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1158
1159 Ok(EncryptedMessage {
1160 group_id: group_id.clone(),
1161 message_type: MlsMessageType::Commit,
1162 epoch: group.epoch().as_u64(),
1163 ciphertext,
1164 sender_id: self.user_id.clone(),
1165 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1166 })
1167 }
1168
1169 pub fn leave_group(&self, group_id: &GroupId) -> Result<()> {
1171 self.group_manager.delete_group(group_id)?;
1172 info!(group_id = %group_id, "Left group");
1173 Ok(())
1174 }
1175
1176 pub fn encrypt_for_group(
1178 &self,
1179 group_id: &GroupId,
1180 plaintext: &[u8],
1181 ) -> Result<EncryptedMessage> {
1182 let mut group = self
1183 .group_manager
1184 .load_group(group_id)?
1185 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1186
1187 let signature_keys = self.get_signer()?;
1188 let mls_message =
1189 self.group_manager
1190 .encrypt_message(&mut group, plaintext, &signature_keys)?;
1191
1192 self.group_manager.save_group(group_id, &group)?;
1193
1194 let ciphertext = mls_message
1195 .tls_serialize_detached()
1196 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1197
1198 Ok(EncryptedMessage {
1199 group_id: group_id.clone(),
1200 message_type: MlsMessageType::Application,
1201 epoch: group.epoch().as_u64(),
1202 ciphertext,
1203 sender_id: self.user_id.clone(),
1204 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1205 })
1206 }
1207
1208 pub fn decrypt_from_group(
1216 &self,
1217 encrypted: &EncryptedMessage,
1218 claimed_sender: &str,
1219 ) -> Result<Option<Vec<u8>>> {
1220 let mut group = self
1221 .group_manager
1222 .load_group(&encrypted.group_id)?
1223 .ok_or_else(|| MlsError::GroupNotFound(encrypted.group_id.to_string()))?;
1224
1225 let mls_message = MlsMessageIn::tls_deserialize_exact(&encrypted.ciphertext)
1226 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1227
1228 let result = self.group_manager.decrypt_message(
1229 &mut group,
1230 &encrypted.group_id,
1231 mls_message,
1232 claimed_sender,
1233 )?;
1234
1235 self.group_manager.save_group(&encrypted.group_id, &group)?;
1236
1237 Ok(result)
1238 }
1239
1240 pub fn join_group(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1242 if welcome.group_id.as_str().starts_with("session:") {
1254 return Err(MlsError::ReservedSessionNamespace {
1255 group_id: welcome.group_id.to_string(),
1256 });
1257 }
1258
1259 let mls_msg = MlsMessageIn::tls_deserialize_exact(&welcome.welcome_data)
1260 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1261
1262 let welcome_msg = match mls_msg.extract() {
1263 MlsMessageBodyIn::Welcome(w) => w,
1264 _ => {
1265 return Err(MlsError::WelcomeProcessing(
1266 "Not a Welcome message".to_string(),
1267 ))
1268 }
1269 };
1270
1271 let group = self
1272 .group_manager
1273 .join_group(welcome_msg, &welcome.group_id)?;
1274 let mut info = self.group_manager.get_group_info(&group, &welcome.group_id);
1275 info.name = welcome.group_name.clone();
1276
1277 info!(group_id = %welcome.group_id, "Joined group");
1278 Ok(info)
1279 }
1280
1281 pub fn list_groups(&self) -> Result<Vec<GroupId>> {
1283 let all_groups = self.group_manager.list_groups()?;
1284 Ok(all_groups
1285 .into_iter()
1286 .filter(|g| !g.as_str().starts_with("session:"))
1287 .collect())
1288 }
1289
1290 pub fn get_group_info(&self, group_id: &GroupId) -> Result<Option<GroupInfo>> {
1292 let group = match self.group_manager.load_group(group_id)? {
1293 Some(g) => g,
1294 None => return Ok(None),
1295 };
1296
1297 let mut info = self.group_manager.get_group_info(&group, group_id);
1298
1299 if let Some(metadata) = self.load_group_metadata(group_id)? {
1301 info.name = metadata.name;
1302 info.created_at_ms = metadata.created_at_ms;
1303 info.last_activity_ms = metadata.last_activity_ms;
1304 }
1305
1306 Ok(Some(info))
1307 }
1308
1309 pub fn has_group(&self, group_id: &GroupId) -> Result<bool> {
1318 Ok(self.group_manager.load_group(group_id)?.is_some())
1319 }
1320
1321 pub fn set_group_name(&self, group_id: &GroupId, name: &str) -> Result<()> {
1323 let mut metadata = self
1324 .load_group_metadata(group_id)?
1325 .unwrap_or_else(|| GroupMetadata::new(None));
1326 metadata.name = Some(name.to_string());
1327 metadata.touch();
1328 self.save_group_metadata(group_id, &metadata)
1329 }
1330
1331 pub fn get_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1333 self.load_group_metadata(group_id)
1334 }
1335
1336 pub fn set_group_custom_metadata(
1338 &self,
1339 group_id: &GroupId,
1340 key: &str,
1341 value: &str,
1342 ) -> Result<()> {
1343 let mut metadata = self
1344 .load_group_metadata(group_id)?
1345 .unwrap_or_else(|| GroupMetadata::new(None));
1346 metadata.custom.insert(key.to_string(), value.to_string());
1347 metadata.touch();
1348 self.save_group_metadata(group_id, &metadata)
1349 }
1350
1351 pub fn remove_group_custom_metadata(&self, group_id: &GroupId, key: &str) -> Result<()> {
1353 let mut metadata = self
1354 .load_group_metadata(group_id)?
1355 .unwrap_or_else(|| GroupMetadata::new(None));
1356 metadata.custom.remove(key);
1357 metadata.touch();
1358 self.save_group_metadata(group_id, &metadata)
1359 }
1360
1361 pub fn set_member_role(
1363 &self,
1364 group_id: &GroupId,
1365 user_id: &str,
1366 role: GroupRole,
1367 ) -> Result<()> {
1368 let mut metadata = self
1369 .load_group_metadata(group_id)?
1370 .unwrap_or_else(|| GroupMetadata::new(None));
1371 metadata.set_role(user_id, role);
1372 metadata.touch();
1373 self.save_group_metadata(group_id, &metadata)
1374 }
1375
1376 pub fn set_enforce_admin_commits(&mut self, enforce: bool) {
1385 self.group_manager.set_enforce_admin_commits(enforce);
1386 }
1387
1388 pub fn set_group_creator(&self, group_id: &GroupId, creator_id: &str) -> Result<()> {
1402 let mut metadata = self
1403 .load_group_metadata(group_id)?
1404 .unwrap_or_else(|| GroupMetadata::new(None));
1405 if metadata.created_by.is_some() {
1406 return Ok(());
1407 }
1408 metadata.created_by = Some(creator_id.to_string());
1409 metadata.touch();
1410 self.save_group_metadata(group_id, &metadata)
1411 }
1412
1413 pub fn remove_member_role(&self, group_id: &GroupId, user_id: &str) -> Result<()> {
1415 let mut metadata = self
1416 .load_group_metadata(group_id)?
1417 .unwrap_or_else(|| GroupMetadata::new(None));
1418 metadata.remove_role(user_id);
1419 metadata.touch();
1420 self.save_group_metadata(group_id, &metadata)
1421 }
1422
1423 pub fn decrypt(
1433 &self,
1434 encrypted: &EncryptedMessage,
1435 claimed_sender: &str,
1436 ) -> Result<Option<Vec<u8>>> {
1437 if encrypted.group_id.as_str().starts_with("session:") {
1438 self.decrypt_from_user(encrypted, claimed_sender)
1439 } else {
1440 self.decrypt_from_group(encrypted, claimed_sender)
1441 }
1442 }
1443
1444 pub fn process_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1446 if welcome.group_id.as_str().starts_with("session:") {
1447 self.join_session(welcome)
1448 } else {
1449 self.join_group(welcome)
1450 }
1451 }
1452}
1453
1454impl MlsManager {
1455 fn load_stored_key_package(&self, package_id: &str) -> Result<Option<KeyPackageBundle>> {
1461 let key_type = StorageKeyType::KeyPackage.as_str();
1462 let data = match self.storage.load(key_type, package_id)? {
1463 Some(data) => data,
1464 None => return Ok(None),
1465 };
1466
1467 let mut bundle = match serde_json::from_slice::<KeyPackageBundle>(&data) {
1468 Ok(bundle) => bundle,
1469 Err(_) => {
1470 let legacy_bundle = KeyPackageBundle::new(
1471 package_id.to_string(),
1472 self.user_id.clone(),
1473 data,
1474 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
1475 );
1476 let serialized = serde_json::to_vec(&legacy_bundle)
1477 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1478 self.storage.store(key_type, package_id, &serialized)?;
1479 legacy_bundle
1480 }
1481 };
1482
1483 if bundle.is_expired() {
1484 if bundle.expired_past_grace(KEY_PACKAGE_PURGE_GRACE_SECS) {
1491 self.purge_key_package_material(package_id, &bundle)?;
1492 }
1493 return Ok(None);
1494 }
1495
1496 let needs_backfill = bundle.provider_hash_ref.is_none();
1502 let Some(hash_ref) = self.bundle_hash_ref(&bundle) else {
1503 warn!(
1504 package_id = %package_id,
1505 "Key package bytes no longer parse as a key package, pruning stale entry"
1506 );
1507 self.storage.delete(key_type, package_id)?;
1508 return Ok(None);
1509 };
1510 if !self.provider_has_init_key(&hash_ref) {
1511 warn!(
1512 package_id = %package_id,
1513 "Key package private key no longer in provider storage, pruning stale entry"
1514 );
1515 self.storage.delete(key_type, package_id)?;
1516 return Ok(None);
1517 }
1518
1519 if needs_backfill {
1520 if let Ok(bytes) = hash_ref.tls_serialize_detached() {
1521 bundle.provider_hash_ref = Some(bytes);
1522 let serialized = serde_json::to_vec(&bundle)
1523 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1524 self.storage.store(key_type, package_id, &serialized)?;
1525 }
1526 }
1527
1528 Ok(Some(bundle))
1529 }
1530
1531 fn purge_key_package_material(
1546 &self,
1547 package_id: &str,
1548 bundle: &KeyPackageBundle,
1549 ) -> Result<()> {
1550 if let Some(hash_ref) = self.bundle_hash_ref(bundle) {
1551 use openmls_traits::storage::StorageProvider;
1552 if let Err(e) = self.provider.storage().delete_key_package(&hash_ref) {
1553 warn!(
1554 package_id = %package_id,
1555 error = %e,
1556 "Failed to delete a key package's private init key; keeping the record to retry"
1557 );
1558 return Ok(());
1559 }
1560 }
1561
1562 self.storage
1563 .delete(StorageKeyType::KeyPackage.as_str(), package_id)?;
1564 debug!(package_id = %package_id, "Purged key package and its init key");
1565 Ok(())
1566 }
1567
1568 fn key_package_hash_ref(&self, key_package_data: &[u8]) -> Option<KeyPackageRef> {
1575 let kp_in = KeyPackageIn::tls_deserialize_exact(key_package_data).ok()?;
1576 let kp: KeyPackage = kp_in
1577 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
1578 .ok()?;
1579 kp.hash_ref(self.provider.crypto()).ok()
1580 }
1581
1582 fn bundle_hash_ref(&self, bundle: &KeyPackageBundle) -> Option<KeyPackageRef> {
1586 bundle
1587 .provider_hash_ref
1588 .as_deref()
1589 .and_then(|bytes| KeyPackageRef::tls_deserialize_exact(bytes).ok())
1590 .or_else(|| self.key_package_hash_ref(&bundle.key_package_data))
1591 }
1592
1593 fn provider_has_init_key(&self, hash_ref: &KeyPackageRef) -> bool {
1597 use openmls_traits::storage::StorageProvider;
1598 let found: std::result::Result<Option<openmls::key_packages::KeyPackageBundle>, _> =
1599 self.provider.storage().key_package(hash_ref);
1600 matches!(found, Ok(Some(_)))
1601 }
1602
1603 #[cfg(test)]
1608 fn is_key_package_usable(&self, key_package_data: &[u8]) -> bool {
1609 self.key_package_hash_ref(key_package_data)
1610 .map(|hash_ref| self.provider_has_init_key(&hash_ref))
1611 .unwrap_or(false)
1612 }
1613
1614 fn load_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1616 let key_type = StorageKeyType::GroupMetadata.as_str();
1617 match self.storage.load(key_type, group_id.as_str())? {
1618 Some(data) => {
1619 let mut metadata: GroupMetadata = serde_json::from_slice(&data)
1620 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1621 if metadata.roles.is_empty()
1623 && metadata
1624 .custom
1625 .keys()
1626 .any(|k| k.starts_with(GroupMetadata::LEGACY_ROLE_KEY_PREFIX))
1627 {
1628 metadata.migrate_legacy_roles();
1629 if let Err(e) = self.save_group_metadata(group_id, &metadata) {
1631 warn!(group_id = %group_id.as_str(), error = %e, "Failed to persist legacy role migration");
1632 }
1633 }
1634 Ok(Some(metadata))
1635 }
1636 None => Ok(None),
1637 }
1638 }
1639
1640 fn save_group_metadata(&self, group_id: &GroupId, metadata: &GroupMetadata) -> Result<()> {
1642 let key_type = StorageKeyType::GroupMetadata.as_str();
1643 let data =
1644 serde_json::to_vec(metadata).map_err(|e| MlsError::Serialization(e.to_string()))?;
1645 self.storage.store(key_type, group_id.as_str(), &data)?;
1646 Ok(())
1647 }
1648}
1649
1650impl MlsManager {
1655 pub fn update_keys(&self, group_id: &GroupId) -> Result<EncryptedMessage> {
1664 use openmls::treesync::LeafNodeParameters;
1665
1666 let mut group = self
1667 .group_manager
1668 .load_group(group_id)?
1669 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1670
1671 let signature_keys = self.get_signer()?;
1672
1673 let bundle = group
1674 .self_update(
1675 &self.provider,
1676 &signature_keys,
1677 LeafNodeParameters::default(),
1678 )
1679 .map_err(|e| MlsError::OpenMls(format!("Self-update failed: {}", e)))?;
1680
1681 let (commit, _welcome, _group_info) = bundle.into_contents();
1682
1683 group
1684 .merge_pending_commit(&self.provider)
1685 .map_err(|e| MlsError::OpenMls(format!("Failed to merge self-update commit: {}", e)))?;
1686
1687 self.group_manager.save_group(group_id, &group)?;
1688
1689 if let Some(mut metadata) = self.load_group_metadata(group_id)? {
1691 metadata.touch();
1692 self.save_group_metadata(group_id, &metadata)?;
1693 }
1694
1695 let ciphertext = commit
1696 .tls_serialize_detached()
1697 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1698
1699 debug!(group_id = %group_id, epoch = %group.epoch().as_u64(), "Updated group keys");
1700
1701 Ok(EncryptedMessage {
1702 group_id: group_id.clone(),
1703 message_type: MlsMessageType::Commit,
1704 epoch: group.epoch().as_u64(),
1705 ciphertext,
1706 sender_id: self.user_id.clone(),
1707 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1708 })
1709 }
1710
1711 pub fn ensure_min_key_packages(&self, min: usize) -> Result<usize> {
1730 let min = min.min(MAX_PUSH_KEY_PACKAGES);
1731 let key_type = StorageKeyType::KeyPackage.as_str();
1732 let package_ids = self.storage.list_keys(key_type)?;
1733
1734 let mut valid_count = 0;
1736 for package_id in &package_ids {
1737 if self.load_stored_key_package(package_id)?.is_some() {
1738 valid_count += 1;
1739 }
1740 }
1741
1742 let to_generate = min.saturating_sub(valid_count);
1744 for _ in 0..to_generate {
1745 self.generate_key_package()?;
1746 valid_count += 1;
1747 }
1748
1749 debug!(
1750 valid_count = valid_count,
1751 generated = to_generate,
1752 "Ensured minimum key packages"
1753 );
1754
1755 Ok(valid_count)
1756 }
1757
1758 pub fn count_valid_key_packages(&self) -> Result<usize> {
1760 let key_type = StorageKeyType::KeyPackage.as_str();
1761 let package_ids = self.storage.list_keys(key_type)?;
1762
1763 let mut count = 0;
1764 for package_id in package_ids {
1765 if self.load_stored_key_package(&package_id)?.is_some() {
1766 count += 1;
1767 }
1768 }
1769
1770 Ok(count)
1771 }
1772
1773 pub fn get_identity_public_key(&self) -> Result<Vec<u8>> {
1782 let credential = self.get_credential()?;
1783 Ok(credential.signature_key.as_slice().to_vec())
1784 }
1785
1786 pub fn sign_data(&self, data: &[u8]) -> Result<Vec<u8>> {
1799 use openmls_traits::signatures::Signer;
1800
1801 let signer = self.get_signer()?;
1802 let signature = signer
1803 .sign(data)
1804 .map_err(|e| MlsError::Signing(format!("Failed to sign data: {:?}", e)))?;
1805 Ok(signature.as_slice().to_vec())
1806 }
1807
1808 pub fn verify_signature(public_key: &[u8], data: &[u8], signature: &[u8]) -> Result<bool> {
1820 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
1821
1822 let verifying_key = VerifyingKey::try_from(public_key).map_err(|e| {
1824 MlsError::InvalidPublicKey(format!("Invalid Ed25519 public key: {}", e))
1825 })?;
1826
1827 let sig = Signature::try_from(signature).map_err(|e| {
1829 MlsError::VerificationFailed(format!("Invalid signature format: {}", e))
1830 })?;
1831
1832 match verifying_key.verify(data, &sig) {
1834 Ok(()) => Ok(true),
1835 Err(_) => Ok(false),
1836 }
1837 }
1838
1839 pub fn derive_address(public_key: &[u8]) -> Result<Address> {
1865 offline_protocol_sealed::derive_address(public_key).map_err(MlsError::from)
1866 }
1867
1868 #[deprecated(
1888 note = "use derive_address; this returns the self-certifying off1… address, no longer base58"
1889 )]
1890 pub fn derive_user_id_from_public_key(public_key: &[u8]) -> String {
1891 use sha2::{Digest, Sha256};
1892
1893 let hash = Sha256::digest(public_key);
1894 let mut truncated = [0u8; Address::HASH_LEN];
1895 truncated.copy_from_slice(&hash[..Address::HASH_LEN]);
1896 Address::from_hash_bytes(truncated).to_string()
1897 }
1898}
1899
1900#[cfg(feature = "test-utils")]
1909impl MlsManager {
1910 pub fn seat_forged_leaf_for_testing(
1924 &self,
1925 group_id: &GroupId,
1926 impersonated: &str,
1927 ) -> Result<Vec<u8>> {
1928 let forged_storage: Arc<dyn MlsStorage> = Arc::new(crate::storage::InMemoryStorage::new());
1929 let forged_provider = MlsProvider::new(MlsStorageAdapter::new(forged_storage));
1930 let forged_keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
1931 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
1932 forged_keys
1933 .store(forged_provider.storage())
1934 .map_err(|e| MlsError::CryptoGeneration(format!("storing forged key: {:?}", e)))?;
1935
1936 let credential = CredentialWithKey {
1937 credential: Credential::new(CredentialType::Basic, impersonated.as_bytes().to_vec()),
1938 signature_key: forged_keys.public().into(),
1939 };
1940 let key_package = KeyPackage::builder()
1941 .build(
1942 DEFAULT_CIPHERSUITE,
1943 &forged_provider,
1944 &forged_keys,
1945 credential,
1946 )
1947 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?
1948 .key_package()
1949 .tls_serialize_detached()
1950 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1951
1952 let validated = KeyPackageIn::tls_deserialize_exact(&key_package)
1953 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?
1954 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
1955 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
1956
1957 let mut group = self
1958 .group_manager
1959 .load_group(group_id)?
1960 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1961 let signer = self.get_signer()?;
1962 let (commit, _welcome) = self
1963 .group_manager
1964 .add_member(&mut group, validated, &signer)?;
1965 self.group_manager.save_group(group_id, &group)?;
1966
1967 commit
1968 .tls_serialize_detached()
1969 .map_err(|e| MlsError::Serialization(e.to_string()))
1970 }
1971}
1972
1973pub fn is_mls_framed(bytes: &[u8]) -> bool {
1982 MlsMessageIn::tls_deserialize_exact(bytes).is_ok()
1983}
1984
1985#[cfg(test)]
1986mod tests {
1987 use super::*;
1988 use crate::storage::InMemoryStorage;
1989
1990 fn create_test_manager(label: &str) -> MlsManager {
1999 create_addressed_manager(label).0
2000 }
2001
2002 fn addr(label: &str) -> String {
2004 test_identity(label).1.to_string()
2005 }
2006
2007 fn slot(a: &str, b: &str) -> GroupId {
2013 GroupId::for_session(&addr(a), &addr(b)).unwrap()
2014 }
2015
2016 pub(crate) fn test_identity(label: &str) -> (SignatureKeyPair, Address) {
2024 use sha2::{Digest, Sha256};
2025
2026 let seed: [u8; 32] = Sha256::digest(label.as_bytes()).into();
2027 let signing = ed25519_dalek::SigningKey::from_bytes(&seed);
2028 let public = signing.verifying_key().to_bytes().to_vec();
2029 let address = MlsManager::derive_address(&public).unwrap();
2030 let keys = SignatureKeyPair::from_raw(
2031 DEFAULT_CIPHERSUITE.signature_algorithm(),
2032 signing.to_bytes().to_vec(),
2033 public,
2034 );
2035 (keys, address)
2036 }
2037
2038 pub(crate) fn seed_identity(storage: &Arc<dyn MlsStorage>, keys: &SignatureKeyPair) {
2041 storage
2042 .store(
2043 StorageKeyType::Identity.as_str(),
2044 "key_pair",
2045 &serde_json::to_vec(keys).unwrap(),
2046 )
2047 .unwrap();
2048 }
2049
2050 fn create_addressed_manager(label: &str) -> (MlsManager, Address) {
2053 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2054 let (keys, address) = test_identity(label);
2055 seed_identity(&storage, &keys);
2056 let manager = MlsManager::new(address.to_string(), storage).unwrap();
2057 (manager, address)
2058 }
2059
2060 fn create_test_manager_with_storage(user_id: &str) -> (MlsManager, Arc<InMemoryStorage>) {
2064 let storage = Arc::new(InMemoryStorage::new());
2065 let manager = MlsManager::new(user_id, storage.clone()).unwrap();
2066 (manager, storage)
2067 }
2068
2069 fn expire_package(storage: &InMemoryStorage, package_id: &str, secs_ago: u64) {
2071 let key_type = StorageKeyType::KeyPackage.as_str();
2072 let data = storage
2073 .load(key_type, package_id)
2074 .unwrap()
2075 .expect("package to age is not in storage");
2076 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2077 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
2078 bundle.expires_at_ms = now_ms - (secs_ago * 1000);
2079 storage
2080 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2081 .unwrap();
2082 }
2083
2084 fn init_key_present(manager: &MlsManager, bundle: &KeyPackageBundle) -> bool {
2086 manager.is_key_package_usable(&bundle.key_package_data)
2087 }
2088
2089 fn strip_cached_ref(storage: &InMemoryStorage, package_id: &str) {
2092 let key_type = StorageKeyType::KeyPackage.as_str();
2093 let data = storage
2094 .load(key_type, package_id)
2095 .unwrap()
2096 .expect("package to strip is not in storage");
2097 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2098 bundle.provider_hash_ref = None;
2099 storage
2100 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2101 .unwrap();
2102 }
2103
2104 #[test]
2109 fn test_minted_package_carries_its_provider_hash_ref() {
2110 let manager = create_test_manager("alice");
2111 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2112
2113 let cached = bundle
2114 .provider_hash_ref
2115 .as_deref()
2116 .expect("mint must stamp the provider hash ref");
2117 let derived = manager
2118 .key_package_hash_ref(&bundle.key_package_data)
2119 .expect("freshly minted package bytes must parse")
2120 .tls_serialize_detached()
2121 .unwrap();
2122 assert_eq!(
2123 cached,
2124 &derived[..],
2125 "cached ref disagrees with the one derived from the package bytes"
2126 );
2127 }
2128
2129 #[test]
2132 fn test_record_without_cached_ref_is_backfilled_on_first_load() {
2133 let (manager, storage) = create_test_manager_with_storage("alice");
2134 let minted = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2135 strip_cached_ref(&storage, &minted.package_id);
2136
2137 let loaded = manager
2138 .key_package_by_id(&minted.package_id)
2139 .unwrap()
2140 .expect("a record without a cached ref must still load");
2141 assert_eq!(
2142 loaded.provider_hash_ref, minted.provider_hash_ref,
2143 "load must hand back a bundle stamped with the same ref the mint computed"
2144 );
2145
2146 let stored: KeyPackageBundle = serde_json::from_slice(
2147 &storage
2148 .load(StorageKeyType::KeyPackage.as_str(), &minted.package_id)
2149 .unwrap()
2150 .unwrap(),
2151 )
2152 .unwrap();
2153 assert_eq!(
2154 stored.provider_hash_ref, minted.provider_hash_ref,
2155 "the backfilled ref must be persisted, not only returned"
2156 );
2157 }
2158
2159 #[test]
2162 fn test_purge_destroys_init_key_for_a_record_without_cached_ref() {
2163 let (manager, storage) = create_test_manager_with_storage("alice");
2164 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2165 strip_cached_ref(&storage, &bundle.package_id);
2166 expire_package(
2167 &storage,
2168 &bundle.package_id,
2169 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2170 );
2171
2172 assert!(manager
2173 .key_package_by_id(&bundle.package_id)
2174 .unwrap()
2175 .is_none());
2176 assert!(
2177 !init_key_present(&manager, &bundle),
2178 "the private init key outlived a pre-cache package"
2179 );
2180 }
2181
2182 #[test]
2187 fn test_expired_package_is_withdrawn_but_its_init_key_survives_the_grace_window() {
2188 let (manager, storage) = create_test_manager_with_storage("alice");
2189 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2190
2191 expire_package(&storage, &bundle.package_id, 60);
2192
2193 assert!(
2194 manager
2195 .key_package_by_id(&bundle.package_id)
2196 .unwrap()
2197 .is_none(),
2198 "an expired package must not be handed out"
2199 );
2200 assert!(
2201 init_key_present(&manager, &bundle),
2202 "the init key was destroyed on the expiry edge, breaking a late Welcome"
2203 );
2204
2205 let replacement = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2207 assert_ne!(replacement.package_id, bundle.package_id);
2208 }
2209
2210 #[test]
2215 fn test_package_past_the_grace_window_has_its_init_key_destroyed() {
2216 let (manager, storage) = create_test_manager_with_storage("alice");
2217 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2218 assert!(init_key_present(&manager, &bundle));
2219
2220 expire_package(
2221 &storage,
2222 &bundle.package_id,
2223 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2224 );
2225
2226 assert!(manager
2228 .key_package_by_id(&bundle.package_id)
2229 .unwrap()
2230 .is_none());
2231
2232 assert!(
2233 !init_key_present(&manager, &bundle),
2234 "the private init key outlived its package"
2235 );
2236 assert!(
2237 storage
2238 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2239 .unwrap()
2240 .is_none(),
2241 "the bundle record outlived its key material"
2242 );
2243 }
2244
2245 #[test]
2249 fn test_delete_key_package_destroys_the_init_key() {
2250 let manager = create_test_manager("alice");
2251 let bundle = manager.generate_publication_key_package().unwrap();
2252 assert!(init_key_present(&manager, &bundle));
2253
2254 manager.delete_key_package(&bundle.package_id).unwrap();
2255
2256 assert!(
2257 !init_key_present(&manager, &bundle),
2258 "delete_key_package left the private init key in provider storage"
2259 );
2260 }
2261
2262 #[test]
2267 fn test_delete_key_package_destroys_the_init_key_of_a_legacy_record() {
2268 let (manager, storage) = create_test_manager_with_storage("alice");
2269 let bundle = manager.generate_publication_key_package().unwrap();
2270 storage
2272 .store(
2273 StorageKeyType::KeyPackage.as_str(),
2274 &bundle.package_id,
2275 &bundle.key_package_data,
2276 )
2277 .unwrap();
2278
2279 manager.delete_key_package(&bundle.package_id).unwrap();
2280
2281 assert!(
2282 !init_key_present(&manager, &bundle),
2283 "a legacy record's private init key survived its deletion"
2284 );
2285 assert!(
2286 storage
2287 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2288 .unwrap()
2289 .is_none(),
2290 "the legacy record outlived its key material"
2291 );
2292 }
2293
2294 #[test]
2298 fn test_delete_key_package_removes_an_unreadable_record() {
2299 let (manager, storage) = create_test_manager_with_storage("alice");
2300 storage
2301 .store(
2302 StorageKeyType::KeyPackage.as_str(),
2303 "junk",
2304 b"not a package",
2305 )
2306 .unwrap();
2307
2308 manager.delete_key_package("junk").unwrap();
2309
2310 assert!(storage
2311 .load(StorageKeyType::KeyPackage.as_str(), "junk")
2312 .unwrap()
2313 .is_none());
2314 }
2315
2316 #[test]
2322 fn test_ensure_min_key_packages_is_capped_at_the_pool_ceiling() {
2323 let manager = create_test_manager("alice");
2324
2325 let count = manager
2326 .ensure_min_key_packages(MAX_PUSH_KEY_PACKAGES + 50)
2327 .unwrap();
2328
2329 assert_eq!(count, MAX_PUSH_KEY_PACKAGES);
2330 assert_eq!(
2331 manager.count_valid_key_packages().unwrap(),
2332 MAX_PUSH_KEY_PACKAGES,
2333 "pre-filling minted past the ceiling the push path enforces"
2334 );
2335 }
2336
2337 #[test]
2338 fn test_manager_creation() {
2339 let manager = create_test_manager("alice");
2340 assert_eq!(manager.user_id(), addr("alice"));
2341 }
2342
2343 #[test]
2344 fn test_key_package_generation() {
2345 let manager = create_test_manager("alice");
2346 let package = manager.generate_key_package().unwrap();
2347
2348 assert_eq!(package.user_id, addr("alice"));
2349 assert!(!package.key_package_data.is_empty());
2350 assert!(!package.is_expired());
2351 }
2352
2353 #[test]
2358 fn test_get_or_create_never_returns_a_publication_package() {
2359 let manager = create_test_manager("alice");
2360 let reserved = manager.generate_publication_key_package().unwrap();
2361 assert!(reserved.reserved_for_publication);
2362
2363 let pushed = manager.get_or_create_key_package().unwrap();
2364 assert_ne!(
2365 pushed.package_id, reserved.package_id,
2366 "the push path took the package a published record is standing on"
2367 );
2368 assert!(!pushed.reserved_for_publication);
2369
2370 let pushed_again = manager.get_or_create_key_package().unwrap();
2373 assert_eq!(pushed.package_id, pushed_again.package_id);
2374 }
2375
2376 #[test]
2380 fn test_key_package_by_id_reports_presence() {
2381 let manager = create_test_manager("alice");
2382 let bundle = manager.generate_publication_key_package().unwrap();
2383
2384 let found = manager.key_package_by_id(&bundle.package_id).unwrap();
2385 assert!(found.is_some());
2386 assert_eq!(found.unwrap().package_id, bundle.package_id);
2387
2388 assert!(manager
2389 .key_package_by_id("no-such-package")
2390 .unwrap()
2391 .is_none());
2392 }
2393
2394 #[test]
2397 fn test_publication_packages_are_distinct_per_slot() {
2398 let manager = create_test_manager("alice");
2399 let a = manager.generate_publication_key_package().unwrap();
2400 let b = manager.generate_publication_key_package().unwrap();
2401 assert_ne!(a.package_id, b.package_id);
2402 assert_ne!(a.key_package_data, b.key_package_data);
2403 }
2404
2405 #[test]
2406 fn test_get_or_create_key_package() {
2407 let manager = create_test_manager("alice");
2408 let pkg1 = manager.get_or_create_key_package().unwrap();
2409 let pkg2 = manager.get_or_create_key_package().unwrap();
2410 assert_eq!(pkg1.package_id, pkg2.package_id);
2414 }
2415
2416 #[test]
2426 fn test_push_path_gives_each_peer_its_own_key_package() {
2427 let manager = create_test_manager("alice");
2428
2429 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap();
2430 let for_carol = manager.take_push_key_package(&addr("carol")).unwrap();
2431
2432 assert_ne!(
2433 for_bob.bundle.package_id, for_carol.bundle.package_id,
2434 "two peers were advertised the same key package"
2435 );
2436 assert_ne!(
2437 for_bob.bundle.key_package_data, for_carol.bundle.key_package_data,
2438 "two peers were advertised the same init key"
2439 );
2440 assert!(!for_bob.pool_exhausted && !for_carol.pool_exhausted);
2441 assert_eq!(
2442 for_bob.bundle.assigned_peer.as_deref(),
2443 Some(addr("bob").as_str())
2444 );
2445 assert_eq!(
2446 for_carol.bundle.assigned_peer.as_deref(),
2447 Some(addr("carol").as_str())
2448 );
2449 }
2450
2451 #[test]
2456 fn test_push_path_reuses_one_package_for_repeat_pushes_to_a_peer() {
2457 let manager = create_test_manager("alice");
2458
2459 let first = manager.take_push_key_package(&addr("bob")).unwrap();
2460 let second = manager.take_push_key_package(&addr("bob")).unwrap();
2461 let third = manager.take_push_key_package(&addr("bob")).unwrap();
2462
2463 assert_eq!(first.bundle.package_id, second.bundle.package_id);
2464 assert_eq!(first.bundle.package_id, third.bundle.package_id);
2465 assert_eq!(
2466 manager.count_valid_key_packages().unwrap(),
2467 1,
2468 "repeat pushes to one peer minted extra key material"
2469 );
2470 }
2471
2472 #[test]
2477 fn test_push_path_mints_a_fresh_package_after_the_peer_consumed_one() {
2478 let alice = create_test_manager("alice");
2479 let bob = create_test_manager("bob");
2480
2481 let advertised = alice.take_push_key_package(&addr("bob")).unwrap().bundle;
2482 bob.import_key_package(&addr("alice"), &advertised.key_package_data)
2483 .unwrap();
2484 let welcome = bob.create_session(&addr("alice")).unwrap();
2485 alice.join_session(&welcome).unwrap();
2486
2487 let next = alice.take_push_key_package(&addr("bob")).unwrap();
2488 assert_ne!(
2489 next.bundle.package_id, advertised.package_id,
2490 "a consumed package was advertised again"
2491 );
2492 assert!(!next.pool_exhausted);
2493 }
2494
2495 #[test]
2500 fn test_two_peers_can_both_establish_sessions_after_being_pushed_to() {
2501 let alice = create_test_manager("alice");
2502 let bob = create_test_manager("bob");
2503 let carol = create_test_manager("carol");
2504
2505 for (peer, manager) in [(addr("bob"), &bob), (addr("carol"), &carol)] {
2506 let advertised = alice.take_push_key_package(&peer).unwrap().bundle;
2507 manager
2508 .import_key_package(&addr("alice"), &advertised.key_package_data)
2509 .unwrap();
2510 }
2511
2512 let from_bob = bob.create_session(&addr("alice")).unwrap();
2515 let from_carol = carol.create_session(&addr("alice")).unwrap();
2516
2517 alice.join_session(&from_bob).unwrap();
2518 alice
2519 .join_session(&from_carol)
2520 .expect("second peer's Welcome must still be processable");
2521
2522 assert!(alice.has_session(&addr("bob")).unwrap());
2523 assert!(alice.has_session(&addr("carol")).unwrap());
2524 }
2525
2526 #[test]
2529 fn test_push_path_never_takes_a_publication_package() {
2530 let manager = create_test_manager("alice");
2531 let reserved = manager.generate_publication_key_package().unwrap();
2532
2533 for peer in [addr("bob"), addr("carol"), addr("dave")] {
2534 let taken = manager.take_push_key_package(&peer).unwrap();
2535 assert_ne!(
2536 taken.bundle.package_id, reserved.package_id,
2537 "the push path took the package a published record stands on"
2538 );
2539 }
2540 }
2541
2542 #[test]
2546 fn test_get_or_create_never_returns_a_peer_assigned_package() {
2547 let manager = create_test_manager("alice");
2548 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2549
2550 let unclaimed = manager.get_or_create_key_package().unwrap();
2551 assert_ne!(unclaimed.package_id, for_bob.package_id);
2552 assert!(unclaimed.assigned_peer.is_none());
2553 }
2554
2555 #[test]
2559 fn test_push_path_claims_an_unclaimed_package_instead_of_stranding_it() {
2560 let manager = create_test_manager("alice");
2561 let legacy = manager.get_or_create_key_package().unwrap();
2562 assert!(legacy.assigned_peer.is_none());
2563
2564 let taken = manager.take_push_key_package(&addr("bob")).unwrap();
2565 assert_eq!(
2566 taken.bundle.package_id, legacy.package_id,
2567 "the pre-existing package was stranded instead of claimed"
2568 );
2569 assert_eq!(
2570 taken.bundle.assigned_peer.as_deref(),
2571 Some(addr("bob").as_str())
2572 );
2573 assert_eq!(manager.count_valid_key_packages().unwrap(), 1);
2574 }
2575
2576 #[test]
2581 fn test_push_pool_ceiling_shares_a_package_and_reports_it() {
2582 let manager = create_test_manager("alice");
2583
2584 for i in 0..MAX_PUSH_KEY_PACKAGES {
2585 let taken = manager.take_push_key_package(&format!("peer{i}")).unwrap();
2586 assert!(
2587 !taken.pool_exhausted,
2588 "pool reported exhausted at {i}, below the ceiling"
2589 );
2590 }
2591 assert_eq!(
2592 manager.count_valid_key_packages().unwrap(),
2593 MAX_PUSH_KEY_PACKAGES
2594 );
2595
2596 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
2597 assert!(over.pool_exhausted, "ceiling breach was not reported");
2598 assert_eq!(
2599 manager.count_valid_key_packages().unwrap(),
2600 MAX_PUSH_KEY_PACKAGES,
2601 "the pool grew past its ceiling"
2602 );
2603
2604 let established = manager.take_push_key_package("peer0").unwrap();
2607 assert!(!established.pool_exhausted);
2608 assert_eq!(established.bundle.assigned_peer.as_deref(), Some("peer0"));
2609 }
2610
2611 #[test]
2617 fn test_ceiling_claims_an_unclaimed_package_rather_than_sharing() {
2618 let manager = create_test_manager("alice");
2619
2620 for i in 0..(MAX_PUSH_KEY_PACKAGES - 1) {
2621 manager.take_push_key_package(&format!("peer{i}")).unwrap();
2622 }
2623 let unclaimed = manager.get_or_create_key_package().unwrap();
2626 assert_eq!(
2627 manager.count_valid_key_packages().unwrap(),
2628 MAX_PUSH_KEY_PACKAGES,
2629 "test premise: the pool is at the ceiling"
2630 );
2631
2632 let taken = manager.take_push_key_package("late-arrival").unwrap();
2633 assert!(
2634 !taken.pool_exhausted,
2635 "shared a package while a claimable one was in the pool"
2636 );
2637 assert_eq!(
2638 taken.bundle.package_id, unclaimed.package_id,
2639 "the claimable package was passed over"
2640 );
2641 assert_eq!(taken.bundle.assigned_peer.as_deref(), Some("late-arrival"));
2642 assert_eq!(
2643 manager.count_valid_key_packages().unwrap(),
2644 MAX_PUSH_KEY_PACKAGES,
2645 "claiming grew the pool"
2646 );
2647
2648 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
2651 assert!(over.pool_exhausted);
2652 }
2653
2654 #[test]
2655 fn test_import_key_package_binds_credential_identity() {
2656 let alice = create_test_manager("alice");
2657 let mallory = create_test_manager("mallory");
2658 let mallory_kp = mallory.generate_key_package().unwrap();
2659
2660 let err = alice
2663 .import_key_package(&addr("bob"), &mallory_kp.key_package_data)
2664 .unwrap_err();
2665 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
2666
2667 alice
2669 .import_key_package(&addr("mallory"), &mallory_kp.key_package_data)
2670 .unwrap();
2671 }
2672
2673 fn substituted_key_package(claimed: &str, keys: &SignatureKeyPair) -> Vec<u8> {
2683 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2684 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
2685 keys.store(provider.storage()).unwrap();
2686
2687 let credential = CredentialWithKey {
2688 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
2689 signature_key: keys.public().into(),
2690 };
2691
2692 KeyPackage::builder()
2693 .build(DEFAULT_CIPHERSUITE, &provider, keys, credential)
2694 .unwrap()
2695 .key_package()
2696 .tls_serialize_detached()
2697 .unwrap()
2698 }
2699
2700 #[test]
2701 fn test_credential_identity_check_alone_does_not_stop_a_substitution() {
2702 let (mallorys_keys, _) = test_identity("mallory");
2707 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
2708
2709 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2710 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
2711 let key_package = KeyPackageIn::tls_deserialize_exact(&forged)
2712 .unwrap()
2713 .validate(provider.crypto(), ProtocolVersion::Mls10)
2714 .unwrap();
2715
2716 MlsManager::verify_credential_identity(&key_package, &addr("bob"))
2717 .expect("the identity check cannot tell the two bobs apart");
2718 }
2719
2720 #[test]
2721 fn test_substituted_key_package_is_refused_by_the_derivation() {
2722 let alice = create_test_manager("alice");
2727 let real_bob = create_test_manager("bob");
2728 let (mallorys_keys, _) = test_identity("mallory");
2729
2730 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
2731 let err = alice.import_key_package(&addr("bob"), &forged).unwrap_err();
2732 assert!(
2733 matches!(
2734 err,
2735 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
2736 ),
2737 "expected an address mismatch, got {:?}",
2738 err
2739 );
2740
2741 let real_kp = real_bob.generate_key_package().unwrap();
2743 alice
2744 .import_key_package(&addr("bob"), &real_kp.key_package_data)
2745 .expect("the real peer's key package derives to its own address");
2746 }
2747
2748 #[test]
2749 fn test_a_nickname_peer_id_is_refused_rather_than_skipping_the_check() {
2750 let alice = create_test_manager("alice");
2754 let bob_kp = create_test_manager("bob").generate_key_package().unwrap();
2755
2756 let err = alice
2757 .import_key_package("bob", &bob_kp.key_package_data)
2758 .unwrap_err();
2759 assert!(
2760 matches!(
2761 err,
2762 MlsError::CredentialIdentityMismatch { .. } | MlsError::InvalidUserId(_)
2763 ),
2764 "a nickname must be refused, got {:?}",
2765 err
2766 );
2767 }
2768
2769 #[test]
2770 fn test_add_group_member_binds_identity_and_derivation() {
2771 let alice = create_test_manager("alice");
2775 let bob = create_test_manager("bob");
2776 let mallory = create_test_manager("mallory");
2777
2778 let group_id = alice.create_group("Test Group").unwrap().group_id;
2779
2780 let mallory_kp = mallory.generate_key_package().unwrap();
2782 let err = alice
2783 .add_group_member(&group_id, &addr("bob"), &mallory_kp.key_package_data)
2784 .unwrap_err();
2785 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
2786
2787 let (mallorys_keys, _) = test_identity("mallory");
2789 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
2790 let err = alice
2791 .add_group_member(&group_id, &addr("bob"), &forged)
2792 .unwrap_err();
2793 assert!(matches!(err, MlsError::KeyPackageAddressMismatch { .. }));
2794
2795 let bob_kp = bob.generate_key_package().unwrap();
2797 alice
2798 .add_group_member(&group_id, &addr("bob"), &bob_kp.key_package_data)
2799 .expect("the real invitee is admitted");
2800 }
2801
2802 #[test]
2803 fn test_import_key_package_rejects_hostile_user_id() {
2804 let alice = create_test_manager("alice");
2805 let bob = create_test_manager("bob");
2806 let bob_kp = bob.generate_key_package().unwrap();
2807
2808 for hostile in ["", "..", "bob/evil", "bob\\evil", "bob\0evil"] {
2809 let err = alice
2810 .import_key_package(hostile, &bob_kp.key_package_data)
2811 .unwrap_err();
2812 assert!(
2813 matches!(err, MlsError::InvalidUserId(_)),
2814 "expected InvalidUserId for {:?}, got {:?}",
2815 hostile,
2816 err
2817 );
2818 }
2819 }
2820
2821 #[test]
2822 fn test_get_contact_key_package_rejects_poisoned_store() {
2823 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2827 let (alice_keys, alice_address) = test_identity("alice");
2828 seed_identity(&storage, &alice_keys);
2829 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
2830 let mallory = create_test_manager("mallory");
2831 let mallory_kp = mallory.generate_key_package().unwrap();
2832
2833 storage
2834 .store(
2835 StorageKeyType::ContactKeyPackage.as_str(),
2836 &addr("bob"),
2837 &mallory_kp.key_package_data,
2838 )
2839 .unwrap();
2840
2841 let err = alice.create_session(&addr("bob")).unwrap_err();
2843 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
2844 }
2845
2846 #[test]
2865 fn test_get_contact_key_package_rejects_a_substituted_package() {
2866 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2867 let (alice_keys, alice_address) = test_identity("alice");
2868 seed_identity(&storage, &alice_keys);
2869 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
2870
2871 let (mallorys_keys, _) = test_identity("mallory");
2873 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
2874
2875 storage
2876 .store(
2877 StorageKeyType::ContactKeyPackage.as_str(),
2878 &addr("bob"),
2879 &forged,
2880 )
2881 .unwrap();
2882
2883 let err = alice.create_session(&addr("bob")).unwrap_err();
2884 assert!(
2885 matches!(
2886 err,
2887 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
2888 ),
2889 "a substituted cached package must be refused at use time, got {:?}",
2890 err
2891 );
2892
2893 assert!(!alice.has_session(&addr("bob")).unwrap());
2895 }
2896
2897 #[test]
2898 fn test_replace_session_with_welcome_rejects_hostile_inviter_id() {
2899 let alice = create_test_manager("alice");
2903 let welcome = WelcomeMessage {
2904 group_id: slot("alice", "bob"),
2905 welcome_data: vec![],
2906 inviter_id: "../../etc".to_string(),
2907 group_name: None,
2908 timestamp_ms: 0,
2909 };
2910 let err = alice.replace_session_with_welcome(&welcome).unwrap_err();
2911 assert!(matches!(err, MlsError::InvalidUserId(_)));
2912 }
2913
2914 fn create_test_session() -> (MlsManager, MlsManager) {
2916 let alice = create_test_manager("alice");
2917 let bob = create_test_manager("bob");
2918 let bob_kp = bob.generate_key_package().unwrap();
2919 alice
2920 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
2921 .unwrap();
2922 let welcome = alice.create_session(&addr("bob")).unwrap();
2923 bob.join_session(&welcome).unwrap();
2924 (alice, bob)
2925 }
2926
2927 #[test]
2928 fn test_session_decrypt_rejects_spoofed_sender() {
2929 let (alice, bob) = create_test_session();
2930
2931 let ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
2937 let err = bob.decrypt_from_user(&ct, &addr("mallory")).unwrap_err();
2938 assert!(
2939 matches!(err, MlsError::SessionIdentityMismatch { .. }),
2940 "spoofed sender must be rejected by the slot binding, got {:?}",
2941 err
2942 );
2943
2944 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
2949 assert_eq!(pt.as_deref(), Some(&b"hello"[..]));
2950
2951 }
2955
2956 #[test]
2957 fn test_epoch_fork_classifies_as_session_desync() {
2958 let (alice, bob) = create_test_session();
2963
2964 let group_id = slot("alice", "bob");
2967 alice.update_keys(&group_id).unwrap();
2968
2969 let ct = alice.encrypt_for_user(&addr("bob"), b"after-fork").unwrap();
2972 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
2973 assert!(
2974 matches!(err, MlsError::SessionDesync(_)),
2975 "epoch fork must classify as SessionDesync, got {:?}",
2976 err
2977 );
2978 }
2979
2980 fn push_varint(out: &mut Vec<u8>, len: usize) {
2993 match len {
2994 0..=63 => out.push(len as u8),
2995 64..=16_383 => {
2996 out.push(0x40 | (len >> 8) as u8);
2997 out.push((len & 0xff) as u8);
2998 }
2999 _ => panic!("test payloads stay under the 2-byte varint ceiling"),
3000 }
3001 }
3002
3003 fn forge_private_message(group_id: &str, epoch: u64) -> Vec<u8> {
3004 let mut out = Vec::new();
3005 out.extend_from_slice(&1u16.to_be_bytes()); out.extend_from_slice(&2u16.to_be_bytes()); push_varint(&mut out, group_id.len());
3008 out.extend_from_slice(group_id.as_bytes());
3009 out.extend_from_slice(&epoch.to_be_bytes());
3010 out.push(1); out.push(0); out.push(8); out.extend_from_slice(&[0u8; 8]);
3014 out.push(16); out.extend_from_slice(&[0u8; 16]);
3016 out
3017 }
3018
3019 #[test]
3020 fn test_forged_frame_reaches_session_desync_without_any_key_material() {
3021 let (_alice, bob) = create_test_session();
3034
3035 let forged = EncryptedMessage {
3036 group_id: slot("alice", "bob"),
3037 message_type: MlsMessageType::Application,
3038 epoch: 9_999,
3039 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
3040 sender_id: addr("alice"),
3041 timestamp_ms: 0,
3042 };
3043
3044 let err = bob.decrypt_from_user(&forged, &addr("alice")).unwrap_err();
3045 assert!(
3046 matches!(err, MlsError::SessionDesync(_)),
3047 "a forged future-epoch frame reaches the desync classification pre-AEAD, got {:?}",
3048 err
3049 );
3050 }
3051
3052 #[test]
3053 fn test_forged_frame_naming_a_foreign_slot_is_rejected_before_desync() {
3054 let (_alice, bob) = create_test_session();
3060
3061 let forged = EncryptedMessage {
3062 group_id: slot("alice", "bob"),
3063 message_type: MlsMessageType::Application,
3064 epoch: 9_999,
3065 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
3066 sender_id: addr("mallory"),
3067 timestamp_ms: 0,
3068 };
3069
3070 let err = bob
3071 .decrypt_from_user(&forged, &addr("mallory"))
3072 .unwrap_err();
3073 assert!(
3074 matches!(err, MlsError::SessionIdentityMismatch { .. }),
3075 "a frame naming another pair's slot must not classify as recoverable, got {:?}",
3076 err
3077 );
3078 }
3079
3080 #[test]
3081 fn test_corrupt_ciphertext_is_not_classified_as_session_desync() {
3082 let (alice, bob) = create_test_session();
3088 let mut ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
3089 for b in ct.ciphertext.iter_mut() {
3091 *b ^= 0xFF;
3092 }
3093 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
3094 assert!(
3095 !matches!(err, MlsError::SessionDesync(_)),
3096 "corrupt ciphertext must not be treated as a recoverable desync, got {:?}",
3097 err
3098 );
3099 }
3100
3101 #[test]
3102 fn test_join_session_binds_slot_to_inviter() {
3103 let bob = create_test_manager("bob");
3110
3111 let hijack = WelcomeMessage {
3114 group_id: slot("alice", "bob"),
3115 welcome_data: vec![], inviter_id: addr("mallory"),
3117 group_name: None,
3118 timestamp_ms: 0,
3119 };
3120
3121 let err = bob.join_session(&hijack).unwrap_err();
3123 assert!(
3124 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3125 "join_session: expected WelcomeIdentityMismatch, got {:?}",
3126 err
3127 );
3128 let err = bob.replace_session_with_welcome(&hijack).unwrap_err();
3129 assert!(
3130 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3131 "replace_session_with_welcome: expected WelcomeIdentityMismatch, got {:?}",
3132 err
3133 );
3134
3135 assert!(!bob.has_session(&addr("alice")).unwrap());
3137 assert!(!bob.has_session(&addr("mallory")).unwrap());
3138
3139 let bob_kp = bob.generate_key_package().unwrap();
3143 let mallory = create_test_manager("mallory");
3144 mallory
3145 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3146 .unwrap();
3147 let legit = mallory.create_session(&addr("bob")).unwrap();
3148 bob.join_session(&legit).unwrap();
3149 assert!(bob.has_session(&addr("mallory")).unwrap());
3150 }
3151
3152 #[test]
3153 fn test_join_group_rejects_session_namespace() {
3154 let bob = create_test_manager("bob");
3162
3163 let squat = WelcomeMessage {
3164 group_id: slot("alice", "bob"),
3165 welcome_data: vec![], inviter_id: addr("mallory"),
3167 group_name: None,
3168 timestamp_ms: 0,
3169 };
3170
3171 let err = bob.join_group(&squat).unwrap_err();
3172 assert!(
3173 matches!(err, MlsError::ReservedSessionNamespace { .. }),
3174 "join_group: expected ReservedSessionNamespace, got {:?}",
3175 err
3176 );
3177
3178 assert!(!bob.has_session(&addr("alice")).unwrap());
3180
3181 let legit_ns = WelcomeMessage {
3185 group_id: GroupId::new("group:0bd6e5f2-3a70-4a4e-9c3f-1c1f2a3b4c5d").unwrap(),
3186 welcome_data: vec![],
3187 inviter_id: addr("mallory"),
3188 group_name: None,
3189 timestamp_ms: 0,
3190 };
3191 let err = bob.join_group(&legit_ns).unwrap_err();
3192 assert!(
3193 !matches!(err, MlsError::ReservedSessionNamespace { .. }),
3194 "a group:-namespaced Welcome must not trip the session-namespace guard, got {:?}",
3195 err
3196 );
3197 }
3198
3199 #[test]
3200 fn test_join_group_rejects_embedded_group_id_mismatch() {
3201 let bob = create_test_manager("bob");
3210 let mallory = create_test_manager("mallory");
3211
3212 let bob_kp = bob.generate_key_package().unwrap();
3214 let embedded = GroupId::new("group:11111111-1111-4111-8111-111111111111").unwrap();
3215 let cred = mallory.get_credential().unwrap();
3216 let signer = mallory.get_signer().unwrap();
3217 mallory
3218 .group_manager
3219 .create_group(&embedded, &cred, &signer)
3220 .unwrap();
3221 let (welcome, _commit) = mallory
3222 .add_group_member(&embedded, &addr("bob"), &bob_kp.key_package_data)
3223 .unwrap();
3224 assert_eq!(welcome.group_id, embedded);
3225
3226 let wire = GroupId::new("group:22222222-2222-4222-8222-222222222222").unwrap();
3230 let tampered = WelcomeMessage {
3231 group_id: wire.clone(),
3232 ..welcome
3233 };
3234
3235 let err = bob.join_group(&tampered).unwrap_err();
3236 assert!(
3237 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3238 "expected WelcomeGroupIdMismatch, got {:?}",
3239 err
3240 );
3241
3242 assert!(bob.group_manager.load_group(&wire).unwrap().is_none());
3244 assert!(bob.group_manager.load_group(&embedded).unwrap().is_none());
3245 }
3246
3247 #[test]
3248 fn test_welcome_embedded_id_cannot_hijack_session_slot() {
3249 let alice = create_test_manager("alice");
3258 let bob = create_test_manager("bob");
3259 let bob_kp = bob.generate_key_package().unwrap();
3260 alice
3261 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3262 .unwrap();
3263 let welcome = alice.create_session(&addr("bob")).unwrap();
3264 bob.join_session(&welcome).unwrap();
3265 assert!(bob.has_session(&addr("alice")).unwrap());
3266
3267 let mallory = create_test_manager("mallory");
3269 let bob_kp2 = bob.generate_key_package().unwrap();
3270 let squat = slot("alice", "bob");
3271 let cred = mallory.get_credential().unwrap();
3272 let signer = mallory.get_signer().unwrap();
3273 mallory
3274 .group_manager
3275 .create_group(&squat, &cred, &signer)
3276 .unwrap();
3277 let (mal_welcome, _commit) = mallory
3278 .add_group_member(&squat, &addr("bob"), &bob_kp2.key_package_data)
3279 .unwrap();
3280
3281 let attack = WelcomeMessage {
3283 group_id: slot("bob", "mallory"),
3284 welcome_data: mal_welcome.welcome_data,
3285 inviter_id: addr("mallory"),
3286 group_name: None,
3287 timestamp_ms: 0,
3288 };
3289
3290 let err = bob.join_session(&attack).unwrap_err();
3291 assert!(
3292 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3293 "expected WelcomeGroupIdMismatch, got {:?}",
3294 err
3295 );
3296
3297 assert!(bob.has_session(&addr("alice")).unwrap());
3299 let ct = bob
3300 .encrypt_for_user(&addr("alice"), b"still private")
3301 .unwrap();
3302 let pt = alice.decrypt_from_user(&ct, &addr("bob")).unwrap();
3303 assert_eq!(pt.as_deref(), Some(&b"still private"[..]));
3304 }
3305
3306 fn create_test_group_with_bob() -> (MlsManager, MlsManager, GroupId) {
3309 let alice = create_test_manager("alice");
3310 let bob = create_test_manager("bob");
3311 let bob_kp = bob.generate_key_package().unwrap();
3312 alice
3313 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3314 .unwrap();
3315 let info = alice.create_group("Test Group").unwrap();
3316 let gid = info.group_id.clone();
3317 let (welcome, _commit) = alice
3318 .add_group_member(&gid, &addr("bob"), &bob_kp.key_package_data)
3319 .unwrap();
3320 bob.join_group(&welcome).unwrap();
3321 (alice, bob, gid)
3322 }
3323
3324 #[test]
3325 fn test_group_decrypt_rejects_spoofed_sender() {
3326 let (alice, bob, gid) = create_test_group_with_bob();
3327
3328 let ct = alice.encrypt_for_group(&gid, b"group message").unwrap();
3329 let err = bob.decrypt_from_group(&ct, &addr("mallory")).unwrap_err();
3330 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3331
3332 let ct2 = alice.encrypt_for_group(&gid, b"another one").unwrap();
3333 let pt = bob.decrypt_from_group(&ct2, &addr("alice")).unwrap();
3334 assert_eq!(pt.as_deref(), Some(&b"another one"[..]));
3335 }
3336
3337 #[test]
3338 fn test_group_commit_with_spoofed_sender_rejected_before_merge() {
3339 let (alice, bob, gid) = create_test_group_with_bob();
3340
3341 let commit = alice.update_keys(&gid).unwrap();
3345 let epoch_before = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3346
3347 let err = bob
3348 .decrypt_from_group(&commit, &addr("mallory"))
3349 .unwrap_err();
3350 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3351
3352 let epoch_after = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3353 assert_eq!(
3354 epoch_before, epoch_after,
3355 "spoofed commit must not advance group state"
3356 );
3357 }
3358
3359 #[test]
3360 fn test_no_session_initially() {
3361 let manager = create_test_manager("alice");
3362 assert!(!manager.has_session(&addr("bob")).unwrap());
3363 assert!(manager.list_sessions().unwrap().is_empty());
3364 }
3365
3366 #[test]
3367 fn test_no_groups_initially() {
3368 let manager = create_test_manager("alice");
3369 assert!(manager.list_groups().unwrap().is_empty());
3370 }
3371
3372 #[test]
3373 fn test_group_creation() {
3374 let manager = create_test_manager("alice");
3375 let info = manager.create_group("Test Group").unwrap();
3376
3377 assert_eq!(info.name, Some("Test Group".to_string()));
3378 assert!(!info.is_session);
3379 assert_eq!(info.members.len(), 1);
3380
3381 let groups = manager.list_groups().unwrap();
3382 assert_eq!(groups.len(), 1);
3383 }
3384
3385 #[test]
3396 fn test_both_create_adopt_is_non_destructive_on_welcome_retransmit() {
3397 let alice = create_test_manager("alice");
3399 let bob = create_test_manager("bob");
3400
3401 let alice_kp = alice.generate_key_package().unwrap();
3403 let bob_kp = bob.generate_key_package().unwrap();
3404 alice
3405 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3406 .unwrap();
3407 bob.import_key_package(&addr("alice"), &alice_kp.key_package_data)
3408 .unwrap();
3409
3410 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());
3414
3415 bob.join_session(&alice_welcome).unwrap();
3418 assert!(bob.has_session(&addr("alice")).unwrap());
3419
3420 let ct = alice
3422 .encrypt_for_user(&addr("bob"), b"hello over the converged group")
3423 .unwrap();
3424 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
3425 assert_eq!(pt.as_deref(), Some(&b"hello over the converged group"[..]));
3426
3427 let retransmit = bob.join_session(&alice_welcome);
3430 assert!(
3431 retransmit.is_err(),
3432 "re-staging a consumed key package should fail"
3433 );
3434
3435 assert!(
3438 bob.has_session(&addr("alice")).unwrap(),
3439 "duplicate Welcome must not brick the converged session"
3440 );
3441
3442 let ct2 = alice
3444 .encrypt_for_user(&addr("bob"), b"still converged after retransmit")
3445 .unwrap();
3446 let pt2 = bob.decrypt_from_user(&ct2, &addr("alice")).unwrap();
3447 assert_eq!(
3448 pt2.as_deref(),
3449 Some(&b"still converged after retransmit"[..])
3450 );
3451 }
3452
3453 #[test]
3459 fn test_out_of_order_decryption_within_sender_ratchet_tolerance() {
3460 let alice = create_test_manager("alice");
3461 let bob = create_test_manager("bob");
3462
3463 let bob_kp = bob.generate_key_package().unwrap();
3464 alice
3465 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3466 .unwrap();
3467 let welcome = alice.create_session(&addr("bob")).unwrap();
3468 bob.join_session(&welcome).unwrap();
3469
3470 let ciphertexts: Vec<_> = (0..40)
3471 .map(|i| {
3472 alice
3473 .encrypt_for_user(&addr("bob"), format!("chunk {}", i).as_bytes())
3474 .unwrap()
3475 })
3476 .collect();
3477
3478 let pt = bob
3481 .decrypt_from_user(&ciphertexts[39], &addr("alice"))
3482 .unwrap();
3483 assert_eq!(pt.as_deref(), Some(&b"chunk 39"[..]));
3484
3485 let pt = bob
3488 .decrypt_from_user(&ciphertexts[10], &addr("alice"))
3489 .unwrap();
3490 assert_eq!(pt.as_deref(), Some(&b"chunk 10"[..]));
3491
3492 let res = bob.decrypt_from_user(&ciphertexts[0], &addr("alice"));
3495 assert!(
3496 !matches!(res, Ok(Some(_))),
3497 "generation beyond the tolerance must not decrypt"
3498 );
3499 }
3500
3501 const RFC8032_TV1_PK: [u8; 32] = [
3503 0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07,
3504 0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07,
3505 0x51, 0x1a,
3506 ];
3507
3508 const RFC8032_TV1_ADDRESS: &str = "off1qysluvwl5922yctzd0u9gpr06gn3k7ldfvgtwgvn";
3513
3514 #[test]
3522 fn derive_address_matches_the_pinned_vector() {
3523 let address = MlsManager::derive_address(&RFC8032_TV1_PK).expect("32-byte key derives");
3524 assert_eq!(address.to_string(), RFC8032_TV1_ADDRESS);
3525 assert_eq!(
3528 RFC8032_TV1_ADDRESS.parse::<Address>().expect("parses"),
3529 address
3530 );
3531 }
3532
3533 #[test]
3534 fn derive_address_rejects_wrong_key_lengths() {
3535 for len in [0usize, 31, 33, 64] {
3536 let key = vec![0u8; len];
3537 assert!(
3538 matches!(
3539 MlsManager::derive_address(&key),
3540 Err(MlsError::InvalidPublicKey(_))
3541 ),
3542 "{len}-byte key must be rejected"
3543 );
3544 }
3545 }
3546
3547 #[test]
3548 fn deprecated_derive_agrees_with_derive_address() {
3549 #[allow(deprecated)]
3552 let legacy = MlsManager::derive_user_id_from_public_key(&RFC8032_TV1_PK);
3553 assert_eq!(legacy, RFC8032_TV1_ADDRESS);
3554 assert_eq!(
3555 legacy,
3556 MlsManager::derive_address(&RFC8032_TV1_PK)
3557 .expect("derives")
3558 .to_string()
3559 );
3560 }
3561
3562 #[test]
3563 fn load_or_create_identity_mints_once_and_is_idempotent() {
3564 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3565
3566 let (first_keys, first_address) = MlsManager::load_or_create_identity(&storage).unwrap();
3567 let (second_keys, second_address) = MlsManager::load_or_create_identity(&storage).unwrap();
3568
3569 assert_eq!(first_keys.public(), second_keys.public());
3571 assert_eq!(first_address, second_address);
3572 assert_eq!(
3573 MlsManager::derive_address(first_keys.public()).unwrap(),
3574 first_address,
3575 "the returned address must be the stored key's derivation"
3576 );
3577 }
3578
3579 #[test]
3580 fn load_or_create_identity_adopts_an_existing_key() {
3581 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3582 let (keys, address) = test_identity("alice");
3583 seed_identity(&storage, &keys);
3584
3585 let (loaded, loaded_address) = MlsManager::load_or_create_identity(&storage).unwrap();
3586
3587 assert_eq!(loaded.public(), keys.public());
3588 assert_eq!(loaded_address, address);
3589 }
3590
3591 #[test]
3592 fn manager_bootstrapped_at_its_derived_address_loads_that_identity() {
3593 let (manager, address) = create_addressed_manager("alice");
3594
3595 assert_eq!(manager.user_id(), address.to_string());
3596 assert_eq!(
3597 MlsManager::derive_address(&manager.get_identity_public_key().unwrap()).unwrap(),
3598 address,
3599 "the manager's identity key must derive to the address it claims"
3600 );
3601 }
3602
3603 #[test]
3608 fn manager_refuses_an_address_whose_key_is_not_the_stored_one() {
3609 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3610 let (alice_keys, alice_address) = test_identity("alice");
3611 let (_, bob_address) = test_identity("bob");
3612 seed_identity(&storage, &alice_keys);
3613
3614 let Err(err) = MlsManager::new(bob_address.to_string(), storage) else {
3615 panic!("a manager must not claim an address it holds no key for");
3616 };
3617
3618 match err {
3619 MlsError::IdentityAddressMismatch { expected, derived } => {
3620 assert_eq!(expected, bob_address.to_string());
3621 assert_eq!(derived, alice_address.to_string());
3622 }
3623 other => panic!("expected IdentityAddressMismatch, got {other:?}"),
3624 }
3625 }
3626
3627 #[test]
3631 fn manager_refuses_to_claim_an_address_with_no_stored_identity() {
3632 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3633 let (_, address) = test_identity("alice");
3634
3635 let Err(err) = MlsManager::new(address.to_string(), storage.clone()) else {
3636 panic!("a manager must not mint a key and claim a different address");
3637 };
3638 assert!(matches!(err, MlsError::IdentityAddressMismatch { .. }));
3639
3640 assert!(
3641 storage
3642 .load(StorageKeyType::Identity.as_str(), "key_pair")
3643 .unwrap()
3644 .is_none(),
3645 "a refused bootstrap must not leave a key behind"
3646 );
3647 }
3648
3649 #[test]
3661 fn manager_with_a_non_address_id_skips_the_binding_check() {
3662 let manager = MlsManager::new("alice", Arc::new(InMemoryStorage::new())).unwrap();
3663 assert_eq!(manager.user_id(), "alice");
3664 }
3665
3666 struct Impostor {
3679 provider: MlsProvider,
3680 keys: SignatureKeyPair,
3681 key_package: Vec<u8>,
3682 group: Option<MlsGroup>,
3683 }
3684
3685 impl Impostor {
3686 fn claiming(claimed: &str, key_label: &str) -> Self {
3689 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3690 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
3691 let (keys, _) = test_identity(key_label);
3692 keys.store(provider.storage()).unwrap();
3693
3694 let credential = CredentialWithKey {
3695 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
3696 signature_key: keys.public().into(),
3697 };
3698 let key_package = KeyPackage::builder()
3699 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
3700 .unwrap()
3701 .key_package()
3702 .tls_serialize_detached()
3703 .unwrap();
3704
3705 Self {
3706 provider,
3707 keys,
3708 key_package,
3709 group: None,
3710 }
3711 }
3712
3713 fn join(&mut self, welcome: &MlsMessageOut) {
3715 let bytes = welcome.tls_serialize_detached().unwrap();
3716 let welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
3717 .unwrap()
3718 .extract()
3719 {
3720 MlsMessageBodyIn::Welcome(w) => w,
3721 _ => panic!("not a welcome"),
3722 };
3723 let cfg = MlsGroupJoinConfig::builder()
3724 .use_ratchet_tree_extension(true)
3725 .build();
3726 self.group = Some(
3727 StagedWelcome::new_from_welcome(&self.provider, &cfg, welcome, None)
3728 .unwrap()
3729 .into_group(&self.provider)
3730 .unwrap(),
3731 );
3732 }
3733
3734 fn merge_commit(&mut self, commit_bytes: &[u8]) {
3740 let group = self.group.as_mut().expect("join first");
3741 let message = MlsMessageIn::tls_deserialize_exact(commit_bytes)
3742 .unwrap()
3743 .try_into_protocol_message()
3744 .unwrap();
3745 let processed = group.process_message(&self.provider, message).unwrap();
3746 match processed.into_content() {
3747 ProcessedMessageContent::StagedCommitMessage(staged) => {
3748 group.merge_staged_commit(&self.provider, *staged).unwrap();
3749 }
3750 _ => panic!("not a commit"),
3751 }
3752 }
3753
3754 fn encrypt(&mut self, plaintext: &[u8]) -> Vec<u8> {
3756 self.group
3757 .as_mut()
3758 .expect("join first")
3759 .create_message(&self.provider, &self.keys, plaintext)
3760 .unwrap()
3761 .tls_serialize_detached()
3762 .unwrap()
3763 }
3764 }
3765
3766 fn add_member_bypassing_checks(
3774 manager: &MlsManager,
3775 group_id: &GroupId,
3776 key_package_bytes: &[u8],
3777 ) -> (MlsMessageOut, MlsMessageOut) {
3778 let mut group = manager
3779 .group_manager
3780 .load_group(group_id)
3781 .unwrap()
3782 .expect("group exists");
3783 let key_package = KeyPackageIn::tls_deserialize_exact(key_package_bytes)
3784 .unwrap()
3785 .validate(
3786 manager.group_manager.provider().crypto(),
3787 ProtocolVersion::Mls10,
3788 )
3789 .unwrap();
3790 let signer = manager.get_signer().unwrap();
3791 let (commit, welcome) = manager
3792 .group_manager
3793 .add_member(&mut group, key_package, &signer)
3794 .unwrap();
3795 manager.group_manager.save_group(group_id, &group).unwrap();
3796 (commit, welcome)
3797 }
3798
3799 fn commit_message(
3800 group_id: &GroupId,
3801 commit: &MlsMessageOut,
3802 sender: &str,
3803 ) -> EncryptedMessage {
3804 EncryptedMessage {
3805 group_id: group_id.clone(),
3806 message_type: MlsMessageType::Commit,
3807 epoch: 0,
3808 ciphertext: commit.tls_serialize_detached().unwrap(),
3809 sender_id: sender.to_string(),
3810 timestamp_ms: 0,
3811 }
3812 }
3813
3814 fn app_message(group_id: &GroupId, ciphertext: Vec<u8>, sender: &str) -> EncryptedMessage {
3815 EncryptedMessage {
3816 group_id: group_id.clone(),
3817 message_type: MlsMessageType::Application,
3818 epoch: 0,
3819 ciphertext,
3820 sender_id: sender.to_string(),
3821 timestamp_ms: 0,
3822 }
3823 }
3824
3825 #[test]
3833 fn test_committed_forged_leaf_is_refused_and_cannot_speak_as_its_victim() {
3834 let mallory = create_test_manager("mallory");
3835 let alice = create_test_manager("alice");
3836 let alice_kp = alice.generate_key_package().unwrap();
3837
3838 let gid = mallory.create_group("Team").unwrap().group_id;
3839 let (welcome, _) = mallory
3840 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
3841 .unwrap();
3842 alice.join_group(&welcome).unwrap();
3843 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
3844
3845 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
3847 assert!(matches!(
3848 mallory
3849 .add_group_member(&gid, &addr("bob"), &impostor.key_package)
3850 .unwrap_err(),
3851 MlsError::KeyPackageAddressMismatch { .. }
3852 ));
3853 let (commit, forged_welcome) =
3854 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
3855
3856 let err = alice
3858 .decrypt_from_group(
3859 &commit_message(&gid, &commit, &addr("mallory")),
3860 &addr("mallory"),
3861 )
3862 .unwrap_err();
3863 assert!(
3864 matches!(
3865 err,
3866 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
3867 if *claimed == addr("bob") && *context == crate::error::LeafSource::CommitAdd
3868 ),
3869 "expected a CommitAdd binding refusal, got {err:?}"
3870 );
3871
3872 let after = alice.get_group_info(&gid).unwrap().unwrap();
3874 assert_eq!(
3875 after.epoch, epoch_before,
3876 "a refused commit advanced the epoch"
3877 );
3878 assert!(
3879 !after.members.contains(&addr("bob")),
3880 "roster: {:?}",
3881 after.members
3882 );
3883
3884 impostor.join(&forged_welcome);
3892 let forged = impostor.encrypt(b"send me the recovery phrase");
3893 assert!(
3894 alice
3895 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
3896 .is_err(),
3897 "a forged leaf's message was delivered"
3898 );
3899 }
3900
3901 #[test]
3905 fn test_welcome_carrying_a_forged_leaf_is_refused() {
3906 let mallory = create_test_manager("mallory");
3907 let alice = create_test_manager("alice");
3908
3909 let gid = mallory.create_group("Team").unwrap().group_id;
3910 let impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
3911 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
3912
3913 let alice_kp = alice.generate_key_package().unwrap();
3915 let (welcome, _) = mallory
3916 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
3917 .unwrap();
3918
3919 let err = alice.join_group(&welcome).unwrap_err();
3920 assert!(
3921 matches!(
3922 err,
3923 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
3924 if *claimed == addr("bob") && *context == crate::error::LeafSource::WelcomeTree
3925 ),
3926 "expected a WelcomeTree binding refusal, got {err:?}"
3927 );
3928
3929 assert!(alice.get_group_info(&gid).unwrap().is_none());
3931 }
3932
3933 #[test]
3938 fn test_session_welcome_carrying_a_forged_leaf_is_refused_non_destructively() {
3939 let alice = create_test_manager("alice");
3940 let bob = create_test_manager("bob");
3941 let mallory = create_test_manager("mallory");
3942
3943 let bob_kp = bob.generate_key_package().unwrap();
3945 alice
3946 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3947 .unwrap();
3948 bob.join_session(&alice.create_session(&addr("bob")).unwrap())
3949 .unwrap();
3950 assert!(bob.has_session(&addr("alice")).unwrap());
3951
3952 let bob_kp2 = bob.generate_key_package().unwrap();
3955 let slot_id = slot("bob", "mallory");
3956 let cred = mallory.get_credential().unwrap();
3957 let signer = mallory.get_signer().unwrap();
3958 mallory
3959 .group_manager
3960 .create_group(&slot_id, &cred, &signer)
3961 .unwrap();
3962 let impostor = Impostor::claiming(&addr("carol"), "mallorys-second-key");
3963 add_member_bypassing_checks(&mallory, &slot_id, &impostor.key_package);
3964 let (welcome, _) = mallory
3965 .add_group_member(&slot_id, &addr("bob"), &bob_kp2.key_package_data)
3966 .unwrap();
3967
3968 let err = bob.join_session(&welcome).unwrap_err();
3969 assert!(
3970 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
3971 if *context == crate::error::LeafSource::WelcomeTree),
3972 "expected a WelcomeTree binding refusal, got {err:?}"
3973 );
3974
3975 assert!(bob.has_session(&addr("alice")).unwrap());
3977 let ct = bob
3978 .encrypt_for_user(&addr("alice"), b"still private")
3979 .unwrap();
3980 assert_eq!(
3981 alice
3982 .decrypt_from_user(&ct, &addr("bob"))
3983 .unwrap()
3984 .as_deref(),
3985 Some(&b"still private"[..])
3986 );
3987 }
3988
3989 #[test]
3997 fn test_forged_leaf_seated_behind_the_gates_still_cannot_speak() {
3998 let mallory = create_test_manager("mallory");
3999 let alice = create_test_manager("alice");
4000
4001 let gid = mallory.create_group("Team").unwrap().group_id;
4002 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4003 let (_, forged_welcome) =
4004 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4005 impostor.join(&forged_welcome);
4006
4007 let alice_kp = alice.generate_key_package().unwrap();
4008 let (welcome, add_alice_commit) = mallory
4009 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4010 .unwrap();
4011 impostor.merge_commit(&add_alice_commit.ciphertext);
4014
4015 let bytes = welcome.welcome_data.clone();
4018 let staged_welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
4019 .unwrap()
4020 .extract()
4021 {
4022 MlsMessageBodyIn::Welcome(w) => w,
4023 _ => panic!("not a welcome"),
4024 };
4025 let cfg = MlsGroupJoinConfig::builder()
4026 .use_ratchet_tree_extension(true)
4027 .build();
4028 let group = StagedWelcome::new_from_welcome(&alice.provider, &cfg, staged_welcome, None)
4029 .unwrap()
4030 .into_group(&alice.provider)
4031 .unwrap();
4032 alice.group_manager.save_group(&gid, &group).unwrap();
4033
4034 assert!(
4036 group
4037 .members()
4038 .any(|m| m.credential.serialized_content() == addr("bob").as_bytes()),
4039 "test premise: the forged leaf must be installed"
4040 );
4041
4042 let forged = impostor.encrypt(b"send me the recovery phrase");
4044 let err = alice
4045 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
4046 .unwrap_err();
4047 assert!(
4048 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
4049 if *context == crate::error::LeafSource::MessageSender),
4050 "expected a MessageSender binding refusal, got {err:?}"
4051 );
4052
4053 let info = alice.get_group_info(&gid).unwrap().unwrap();
4056 assert!(
4057 !info.members.contains(&addr("bob")),
4058 "roster surfaced an unproven member: {:?}",
4059 info.members
4060 );
4061 assert!(
4062 info.members.contains(&addr("mallory")),
4063 "{:?}",
4064 info.members
4065 );
4066 }
4067
4068 #[test]
4082 fn test_member_renaming_their_own_leaf_to_a_peers_address_is_refused() {
4083 let (alice, bob, gid) = create_test_group_with_bob();
4087 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
4088
4089 let mut group = bob.group_manager.load_group(&gid).unwrap().unwrap();
4090 let signer = bob.get_signer().unwrap();
4091 let forged = CredentialWithKey {
4092 credential: Credential::new(CredentialType::Basic, addr("alice").as_bytes().to_vec()),
4095 signature_key: signer.public().into(),
4096 };
4097 let params = LeafNodeParameters::builder()
4098 .with_credential_with_key(forged)
4099 .build();
4100 let bundle = group
4101 .self_update(&bob.provider, &signer, params)
4102 .expect("MLS permits a credential change; the AS is what refuses it");
4103 group.merge_pending_commit(&bob.provider).unwrap();
4104 bob.group_manager.save_group(&gid, &group).unwrap();
4105 let (commit, _, _) = bundle.into_contents();
4106
4107 let err = alice
4108 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4109 .unwrap_err();
4110 assert!(
4111 matches!(
4112 err,
4113 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4114 if *claimed == addr("alice") && *context == crate::error::LeafSource::CommitPath
4115 ),
4116 "expected a CommitPath binding refusal, got {err:?}"
4117 );
4118
4119 let after = alice.get_group_info(&gid).unwrap().unwrap();
4122 assert_eq!(after.epoch, epoch_before);
4123 assert!(after.members.contains(&addr("bob")), "{:?}", after.members);
4124 assert_eq!(
4125 after
4126 .members
4127 .iter()
4128 .filter(|m| **m == addr("alice"))
4129 .count(),
4130 1,
4131 "alice must appear once, as herself: {:?}",
4132 after.members
4133 );
4134 }
4135
4136 #[test]
4139 fn test_leaf_with_a_nickname_credential_is_refused() {
4140 let mallory = create_test_manager("mallory");
4141 let alice = create_test_manager("alice");
4142 let alice_kp = alice.generate_key_package().unwrap();
4143
4144 let gid = mallory.create_group("Team").unwrap().group_id;
4145 let impostor = Impostor::claiming("bob", "mallorys-second-key");
4146 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4147 let (welcome, _) = mallory
4148 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4149 .unwrap();
4150
4151 let err = alice.join_group(&welcome).unwrap_err();
4152 assert!(
4153 matches!(err, MlsError::LeafAddressMismatch { ref claimed, .. } if claimed == "bob"),
4154 "a nickname credential was accepted or misreported: {err:?}"
4155 );
4156 }
4157
4158 #[test]
4166 fn test_key_rotation_and_ordinary_traffic_still_pass_the_binding() {
4167 let (alice, bob, gid) = create_test_group_with_bob();
4168
4169 let commit = alice.update_keys(&gid).unwrap();
4170 bob.decrypt_from_group(&commit, &addr("alice"))
4171 .expect("an honest key rotation must still merge");
4172
4173 let ct = alice.encrypt_for_group(&gid, b"after rotation").unwrap();
4174 assert_eq!(
4175 bob.decrypt_from_group(&ct, &addr("alice"))
4176 .unwrap()
4177 .as_deref(),
4178 Some(&b"after rotation"[..])
4179 );
4180
4181 assert!(bob
4183 .get_group_info(&gid)
4184 .unwrap()
4185 .unwrap()
4186 .members
4187 .contains(&addr("bob")));
4188 }
4189
4190 #[test]
4198 fn test_welcome_whose_group_context_has_external_senders_is_refused() {
4199 let mallory = create_test_manager("mallory");
4200 let alice = create_test_manager("alice");
4201
4202 let gid = mallory.create_group("Team").unwrap().group_id;
4203
4204 {
4206 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
4207 let signer = mallory.get_signer().unwrap();
4208 let outsider =
4209 SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
4210 let external = ExternalSender::new(
4211 outsider.public().into(),
4212 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
4213 );
4214 group
4215 .commit_builder()
4216 .propose_group_context_extensions(Extensions::single(Extension::ExternalSenders(
4217 vec![external],
4218 )))
4219 .load_psks(mallory.provider.storage())
4220 .unwrap()
4221 .build(
4222 mallory.provider.rand(),
4223 mallory.provider.crypto(),
4224 &signer,
4225 |_| true,
4226 )
4227 .unwrap()
4228 .stage_commit(&mallory.provider)
4229 .unwrap();
4230 group.merge_pending_commit(&mallory.provider).unwrap();
4231 mallory.group_manager.save_group(&gid, &group).unwrap();
4232 }
4233
4234 let alice_kp = alice.generate_key_package().unwrap();
4235 let (welcome, _) = mallory
4236 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4237 .unwrap();
4238
4239 let err = alice.join_group(&welcome).unwrap_err();
4240 assert!(
4241 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4242 if detail.contains("ExternalSenders")),
4243 "expected the invite to be declined for its ExternalSenders context, got {err:?}"
4244 );
4245 assert!(
4246 alice.get_group_info(&gid).unwrap().is_none(),
4247 "a declined Welcome must not install the group"
4248 );
4249 }
4250
4251 #[test]
4260 fn test_commit_proposing_external_senders_is_refused() {
4261 let (alice, mallory, gid) = create_test_group_with_bob();
4262
4263 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
4264 let signer = mallory.get_signer().unwrap();
4265
4266 let outsider = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
4268 let external = ExternalSender::new(
4269 outsider.public().into(),
4270 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
4271 );
4272 let extensions = Extensions::single(Extension::ExternalSenders(vec![external]));
4273
4274 let bundle = group
4275 .commit_builder()
4276 .propose_group_context_extensions(extensions)
4277 .load_psks(mallory.provider.storage())
4278 .unwrap()
4279 .build(
4280 mallory.provider.rand(),
4281 mallory.provider.crypto(),
4282 &signer,
4283 |_| true,
4284 )
4285 .unwrap()
4286 .stage_commit(&mallory.provider)
4287 .unwrap();
4288 let (commit, _, _) = bundle.into_contents();
4289
4290 let err = alice
4291 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4292 .unwrap_err();
4293 assert!(
4294 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4295 if detail.contains("ExternalSenders")),
4296 "expected an ExternalSenders refusal, got {err:?}"
4297 );
4298 }
4299
4300 #[test]
4310 fn test_one_identity_cannot_hold_two_leaves_in_a_group() {
4311 let alice = create_test_manager("alice");
4312 let bob = create_test_manager("bob");
4313
4314 let gid = alice.create_group("Team").unwrap().group_id;
4315 let first = bob.generate_key_package().unwrap();
4316 alice
4317 .add_group_member(&gid, &addr("bob"), &first.key_package_data)
4318 .unwrap();
4319
4320 let second = bob.generate_key_package().unwrap();
4323 assert_ne!(first.package_id, second.package_id);
4324 let err = alice
4325 .add_group_member(&gid, &addr("bob"), &second.key_package_data)
4326 .unwrap_err();
4327 assert!(
4328 matches!(err, MlsError::AddMember(ref m) if m.contains("Duplicate signature key")),
4329 "expected MLS to refuse a duplicate signature key, got {err:?}"
4330 );
4331
4332 alice.remove_group_member(&gid, &addr("bob")).unwrap();
4334 assert!(!alice
4335 .get_group_info(&gid)
4336 .unwrap()
4337 .unwrap()
4338 .members
4339 .contains(&addr("bob")));
4340 }
4341
4342 #[test]
4358 fn test_external_join_proposal_is_refused_as_an_unsupported_sender() {
4359 let (alice, _bob, gid) = create_test_group_with_bob();
4360
4361 let outsider = create_test_manager("mallory");
4365 let kp_bytes = outsider.generate_key_package().unwrap().key_package_data;
4366 let key_package = KeyPackageIn::tls_deserialize_exact(&kp_bytes)
4367 .unwrap()
4368 .validate(alice.provider.crypto(), ProtocolVersion::Mls10)
4369 .unwrap();
4370 let signer = outsider.get_signer().unwrap();
4371
4372 let (mls_group_id, epoch) = {
4373 let group = alice.group_manager.load_group(&gid).unwrap().unwrap();
4374 (group.group_id().clone(), group.epoch())
4375 };
4376 let proposal =
4377 JoinProposal::new::<MlsStorageAdapter>(key_package, mls_group_id, epoch, &signer)
4378 .expect(
4379 "an external join proposal is well-formed; the SDK just declines to honour it",
4380 );
4381
4382 let err = alice
4383 .decrypt_from_group(
4384 &app_message(
4385 &gid,
4386 proposal.tls_serialize_detached().unwrap(),
4387 &addr("mallory"),
4388 ),
4389 &addr("mallory"),
4390 )
4391 .unwrap_err();
4392 assert!(
4393 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4394 if detail.contains("not a group member")),
4395 "expected an UnsupportedSender refusal, got {err:?}"
4396 );
4397 }
4398
4399 #[test]
4413 fn test_roster_read_counts_the_leaves_it_skips() {
4414 let (alice, _bob, gid) = create_test_group_with_bob();
4415
4416 let before = alice.get_group_info(&gid).unwrap().unwrap();
4417 assert_eq!(
4418 before.unproven_members, 0,
4419 "an honest group must report no unproven members: {:?}",
4420 before.members
4421 );
4422 let honest_members = before.members.len();
4423
4424 let impostor = Impostor::claiming(&addr("carol"), "an-attackers-key");
4427 add_member_bypassing_checks(&alice, &gid, &impostor.key_package);
4428
4429 let after = alice.get_group_info(&gid).unwrap().unwrap();
4430 assert_eq!(
4431 after.unproven_members, 1,
4432 "the skipped leaf must be counted, not only dropped"
4433 );
4434 assert_eq!(
4435 after.members.len(),
4436 honest_members,
4437 "the roster must be unchanged: {:?}",
4438 after.members
4439 );
4440 assert!(
4441 !after.members.contains(&addr("carol")),
4442 "roster surfaced the unproven member: {:?}",
4443 after.members
4444 );
4445 }
4446
4447 fn key_package_with_window(label: &str, lifetime: Lifetime) -> (String, Vec<u8>) {
4457 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4458 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
4459 let (keys, address) = test_identity(label);
4460 keys.store(provider.storage()).unwrap();
4461
4462 let credential = CredentialWithKey {
4463 credential: Credential::new(
4464 CredentialType::Basic,
4465 address.to_string().as_bytes().to_vec(),
4466 ),
4467 signature_key: keys.public().into(),
4468 };
4469 let bytes = KeyPackage::builder()
4470 .key_package_lifetime(lifetime)
4471 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
4472 .unwrap()
4473 .key_package()
4474 .tls_serialize_detached()
4475 .unwrap();
4476
4477 (address.to_string(), bytes)
4478 }
4479
4480 fn now_secs() -> u64 {
4481 std::time::SystemTime::now()
4482 .duration_since(std::time::UNIX_EPOCH)
4483 .unwrap()
4484 .as_secs()
4485 }
4486
4487 #[test]
4490 fn a_year_long_validity_window_is_refused_at_import() {
4491 let (manager, _) = create_addressed_manager("alice");
4492
4493 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
4494
4495 let err = manager
4496 .import_key_package(&peer, &bytes)
4497 .expect_err("a year-long window must be refused");
4498 assert!(
4499 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
4500 "refused for the wrong reason: {err:?}"
4501 );
4502 }
4503
4504 #[test]
4510 fn a_wide_window_written_straight_into_the_store_is_refused_on_read() {
4511 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4512 let (keys, address) = test_identity("alice");
4513 seed_identity(&storage, &keys);
4514 let manager = MlsManager::new(address.to_string(), Arc::clone(&storage)).unwrap();
4515
4516 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
4517 storage
4518 .store(StorageKeyType::ContactKeyPackage.as_str(), &peer, &bytes)
4519 .unwrap();
4520
4521 let err = manager
4522 .get_contact_key_package(&peer)
4523 .expect_err("a cached year-long window must be refused too");
4524 assert!(
4525 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
4526 "refused for the wrong reason: {err:?}"
4527 );
4528 }
4529
4530 #[test]
4537 fn our_own_key_package_states_the_lifetime_we_document() {
4538 let (manager, _) = create_addressed_manager("alice");
4539
4540 let bundle = manager.generate_key_package().unwrap();
4541 let key_package = KeyPackageIn::tls_deserialize_exact(bundle.key_package_data.as_slice())
4542 .unwrap()
4543 .validate(manager.provider.crypto(), ProtocolVersion::Mls10)
4544 .unwrap();
4545
4546 let window = key_package.life_time();
4547 assert_eq!(
4548 window.not_after() - window.not_before(),
4549 DEFAULT_KEY_PACKAGE_LIFETIME_SECS + LIFETIME_SKEW_MARGIN_SECS,
4550 "the window on the wire is not the lifetime this crate declares"
4551 );
4552 assert!(
4553 MlsManager::verify_lifetime_bound(&key_package).is_ok(),
4554 "this install mints a package it would refuse from a peer"
4555 );
4556 }
4557
4558 #[test]
4566 fn a_year_long_validity_window_is_refused_by_a_group_invite() {
4567 let alice = create_test_manager("alice");
4568 let group_id = alice.create_group("Test Group").unwrap().group_id;
4569
4570 let (bob, wide) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
4571 let err = alice
4572 .add_group_member(&group_id, &bob, &wide)
4573 .expect_err("a year-long window must be refused by the group path too");
4574 assert!(
4575 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
4576 "refused for the wrong reason: {err:?}"
4577 );
4578
4579 let bob_manager = create_test_manager("bob");
4582 let bob_kp = bob_manager.generate_key_package().unwrap();
4583 alice
4584 .add_group_member(&group_id, &bob, &bob_kp.key_package_data)
4585 .expect("a package inside the cap is admitted");
4586 }
4587
4588 #[test]
4591 fn a_window_exactly_at_the_cap_is_admitted_and_one_second_over_is_not() {
4592 let cap = MAX_ACCEPTED_KEY_PACKAGE_LIFETIME.as_secs();
4593 let (manager, _) = create_addressed_manager("alice");
4594
4595 let start = now_secs() - 10;
4598
4599 let (at_cap, bytes) = key_package_with_window("bob", Lifetime::init(start, start + cap));
4600 manager
4601 .import_key_package(&at_cap, &bytes)
4602 .expect("a window exactly at the cap is admitted");
4603
4604 let (over_cap, bytes) =
4605 key_package_with_window("carol", Lifetime::init(start, start + cap + 1));
4606 let err = manager
4607 .import_key_package(&over_cap, &bytes)
4608 .expect_err("one second over the cap must be refused");
4609 assert!(
4610 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
4611 "refused for the wrong reason: {err:?}"
4612 );
4613 }
4614}