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> {
443 let key_type = StorageKeyType::KeyPackage.as_str();
444 let packages = self.storage.list_keys(key_type)?;
445
446 for package_id in packages {
447 if let Some(bundle) = self.load_stored_key_package(&package_id)? {
448 if bundle.withheld_from_hand_out() || bundle.assigned_peer.is_some() {
449 continue;
450 }
451 return Ok(bundle);
452 }
453 }
454
455 self.generate_key_package()
456 }
457
458 pub fn take_push_key_package(&self, peer_id: &str) -> Result<PushKeyPackage> {
506 let key_type = StorageKeyType::KeyPackage.as_str();
507 let package_ids = self.storage.list_keys(key_type)?;
508
509 let mut live = 0usize;
510 let mut assigned_here: Option<KeyPackageBundle> = None;
511 let mut unclaimed: Option<KeyPackageBundle> = None;
512 let mut newest: Option<KeyPackageBundle> = None;
513
514 for package_id in package_ids {
518 let Some(bundle) = self.load_stored_key_package(&package_id)? else {
519 continue;
520 };
521 if bundle.withheld_from_hand_out() {
524 continue;
525 }
526 live += 1;
527
528 if bundle.assigned_peer.as_deref() == Some(peer_id) {
529 if newer_than(&bundle, assigned_here.as_ref()) {
531 assigned_here = Some(bundle.clone());
532 }
533 } else if bundle.assigned_peer.is_none() && newer_than(&bundle, unclaimed.as_ref()) {
534 unclaimed = Some(bundle.clone());
535 }
536
537 if newer_than(&bundle, newest.as_ref()) {
538 newest = Some(bundle);
539 }
540 }
541
542 if let Some(bundle) = assigned_here {
543 return Ok(PushKeyPackage {
544 bundle,
545 pool_exhausted: false,
546 });
547 }
548
549 if let Some(bundle) = unclaimed {
552 return Ok(PushKeyPackage {
553 bundle: self.claim_key_package(bundle, peer_id)?,
554 pool_exhausted: false,
555 });
556 }
557
558 if live >= MAX_PUSH_KEY_PACKAGES {
561 if let Some(bundle) = newest {
564 debug!(
565 peer_id = %peer_id,
566 live = live,
567 "Push key-package pool at capacity; reusing an existing package"
568 );
569 return Ok(PushKeyPackage {
570 bundle,
571 pool_exhausted: true,
572 });
573 }
574 }
575
576 let bundle = self.claim_key_package(self.generate_key_package()?, peer_id)?;
577
578 Ok(PushKeyPackage {
579 bundle,
580 pool_exhausted: false,
581 })
582 }
583
584 fn claim_key_package(
586 &self,
587 mut bundle: KeyPackageBundle,
588 peer_id: &str,
589 ) -> Result<KeyPackageBundle> {
590 bundle.assigned_peer = Some(peer_id.to_string());
591 let serialized =
592 serde_json::to_vec(&bundle).map_err(|e| MlsError::Serialization(e.to_string()))?;
593 self.storage.store(
594 StorageKeyType::KeyPackage.as_str(),
595 &bundle.package_id,
596 &serialized,
597 )?;
598 debug!(
599 package_id = %bundle.package_id,
600 peer_id = %peer_id,
601 "Assigned key package to peer"
602 );
603 Ok(bundle)
604 }
605
606 pub fn key_package_by_id(&self, package_id: &str) -> Result<Option<KeyPackageBundle>> {
614 self.load_stored_key_package(package_id)
615 }
616
617 pub fn delete_key_package(&self, package_id: &str) -> Result<()> {
630 let Some(data) = self
631 .storage
632 .load(StorageKeyType::KeyPackage.as_str(), package_id)?
633 else {
634 return Ok(());
635 };
636
637 match serde_json::from_slice::<KeyPackageBundle>(&data) {
638 Ok(bundle) => self.purge_key_package_material(package_id, &bundle),
639 Err(_) => {
647 let legacy = KeyPackageBundle::new(
648 package_id.to_string(),
649 self.user_id.clone(),
650 data,
651 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
652 );
653 self.purge_key_package_material(package_id, &legacy)
654 }
655 }
656 }
657
658 pub fn import_key_package(&self, user_id: &str, key_package_data: &[u8]) -> Result<()> {
673 offline_protocol_core::validate_id_chars(user_id, "User ID")
674 .map_err(|e| MlsError::InvalidUserId(e.to_string()))?;
675
676 let key_package = self.validate_peer_key_package(key_package_data)?;
677
678 Self::verify_credential_identity(&key_package, user_id)?;
679 Self::verify_address_binding(&key_package, user_id)?;
680 Self::verify_lifetime_bound(&key_package)?;
681
682 let key_type = StorageKeyType::ContactKeyPackage.as_str();
683 self.storage.store(key_type, user_id, key_package_data)?;
684
685 debug!(user_id = %user_id, "Imported key package for contact");
686 Ok(())
687 }
688
689 fn get_contact_key_package(&self, user_id: &str) -> Result<KeyPackage> {
691 let key_type = StorageKeyType::ContactKeyPackage.as_str();
692 let data = self
693 .storage
694 .load(key_type, user_id)?
695 .ok_or_else(|| MlsError::NoKeyPackage(user_id.to_string()))?;
696
697 let key_package = self.validate_peer_key_package(&data)?;
699
700 Self::verify_credential_identity(&key_package, user_id)?;
706 Self::verify_address_binding(&key_package, user_id)?;
707 Self::verify_lifetime_bound(&key_package)?;
708
709 Ok(key_package)
710 }
711
712 fn validate_peer_key_package(&self, key_package_data: &[u8]) -> Result<KeyPackage> {
726 KeyPackageIn::tls_deserialize_exact(key_package_data)
727 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?
728 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
729 .map_err(|e| match e {
730 KeyPackageVerifyError::InvalidLifetime => {
731 MlsError::KeyPackageOutsideValidityWindow {
732 expired: Self::window_has_closed(key_package_data),
733 }
734 }
735 other => MlsError::InvalidKeyPackage(other.to_string()),
736 })
737 }
738
739 fn window_has_closed(key_package_data: &[u8]) -> bool {
756 fn find_lifetime(value: &serde_json::Value) -> Option<u64> {
757 match value {
758 serde_json::Value::Object(map) => {
759 if map
760 .get("not_before")
761 .and_then(serde_json::Value::as_u64)
762 .is_some()
763 {
764 if let Some(not_after) =
765 map.get("not_after").and_then(serde_json::Value::as_u64)
766 {
767 return Some(not_after);
768 }
769 }
770 map.values().find_map(find_lifetime)
771 }
772 serde_json::Value::Array(items) => items.iter().find_map(find_lifetime),
773 _ => None,
774 }
775 }
776
777 let Some(not_after) = KeyPackageIn::tls_deserialize_exact(key_package_data)
778 .ok()
779 .and_then(|package| serde_json::to_value(&package).ok())
780 .and_then(|value| find_lifetime(&value))
781 else {
782 return false;
783 };
784 std::time::SystemTime::now()
785 .duration_since(std::time::UNIX_EPOCH)
786 .map(|now| now.as_secs() >= not_after)
787 .unwrap_or(false)
788 }
789
790 fn verify_lifetime_bound(key_package: &KeyPackage) -> Result<()> {
821 let lifetime = key_package.life_time();
822 let width = lifetime.not_after().saturating_sub(lifetime.not_before());
823 let cap = MAX_ACCEPTED_KEY_PACKAGE_LIFETIME.as_secs();
824
825 if width > cap {
826 return Err(MlsError::InvalidKeyPackage(format!(
827 "key package validity window is {} seconds, wider than the {} \
828 seconds this install admits",
829 width, cap
830 )));
831 }
832 Ok(())
833 }
834
835 fn verify_address_binding(key_package: &KeyPackage, user_id: &str) -> Result<()> {
864 let claimed = user_id.parse::<Address>().map_err(|e| {
865 MlsError::InvalidUserId(format!("'{}' is not an address: {}", user_id, e))
866 })?;
867
868 let derived = Self::derive_address(key_package.leaf_node().signature_key().as_slice())?;
869 if derived != claimed {
870 return Err(MlsError::KeyPackageAddressMismatch {
871 claimed: claimed.to_string(),
872 derived: derived.to_string(),
873 });
874 }
875 Ok(())
876 }
877
878 fn verify_credential_identity(key_package: &KeyPackage, user_id: &str) -> Result<()> {
882 let identity = key_package.leaf_node().credential().serialized_content();
883 if identity != user_id.as_bytes() {
884 return Err(MlsError::CredentialIdentityMismatch {
885 expected: user_id.to_string(),
886 found: String::from_utf8_lossy(identity).into_owned(),
887 });
888 }
889 Ok(())
890 }
891
892 pub fn get_pending_key_packages(&self) -> Result<Vec<KeyPackageBundle>> {
902 let key_type = StorageKeyType::KeyPackage.as_str();
903 let package_ids = self.storage.list_keys(key_type)?;
904
905 let mut bundles = Vec::new();
906 for package_id in package_ids {
907 if let Some(bundle) = self.load_stored_key_package(&package_id)? {
908 if bundle.withheld_from_hand_out() || bundle.assigned_peer.is_some() {
909 continue;
910 }
911 bundles.push(bundle);
912 }
913 }
914
915 Ok(bundles)
916 }
917
918 pub fn mark_key_package_synced(&mut self, package_id: &str) -> Result<()> {
945 let Some(mut bundle) = self.load_stored_key_package(package_id)? else {
946 debug!(
947 package_id = %package_id,
948 "No live key package to mark synced"
949 );
950 return Ok(());
951 };
952 if bundle.synced {
953 return Ok(());
954 }
955 if let Some(peer) = bundle.assigned_peer.as_deref() {
956 warn!(
957 package_id = %package_id,
958 peer_id = %peer,
959 "Marking a key package synced that the push path already handed to a peer"
960 );
961 }
962 bundle.synced = true;
963 let serialized =
964 serde_json::to_vec(&bundle).map_err(|e| MlsError::Serialization(e.to_string()))?;
965 self.storage
966 .store(StorageKeyType::KeyPackage.as_str(), package_id, &serialized)?;
967 debug!(package_id = %package_id, "Marked key package as synced");
968 Ok(())
969 }
970
971 pub fn has_session(&self, other_user_id: &str) -> Result<bool> {
977 self.session_manager.has_session(other_user_id)
978 }
979
980 pub fn create_session(&self, other_user_id: &str) -> Result<WelcomeMessage> {
982 let their_key_package = self.get_contact_key_package(other_user_id)?;
983 let credential = self.get_credential()?;
984 let signature_keys = self.get_signer()?;
985
986 let welcome = self.session_manager.create_session(
987 other_user_id,
988 their_key_package,
989 &credential,
990 &signature_keys,
991 )?;
992
993 let key_type = StorageKeyType::ContactKeyPackage.as_str();
994 self.storage.delete(key_type, other_user_id)?;
995
996 Ok(welcome)
997 }
998
999 pub fn join_session(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1001 self.session_manager.join_session(welcome)
1002 }
1003
1004 pub fn replace_session_with_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1011 let other_user_id = &welcome.inviter_id;
1012
1013 offline_protocol_core::validate_id_chars(other_user_id, "User ID")
1017 .map_err(|e| MlsError::InvalidUserId(e.to_string()))?;
1018
1019 self.session_manager.verify_welcome_slot(welcome)?;
1024
1025 let _ = self.clear_pending_welcome(other_user_id);
1027
1028 let key_type = StorageKeyType::ContactKeyPackage.as_str();
1030 let _ = self.storage.delete(key_type, other_user_id);
1031
1032 self.session_manager.join_session(welcome)
1036 }
1037
1038 pub fn encrypt_for_user(
1040 &self,
1041 other_user_id: &str,
1042 plaintext: &[u8],
1043 ) -> Result<EncryptedMessage> {
1044 if !self.has_session(other_user_id)? {
1045 let welcome = self.create_session(other_user_id)?;
1046 warn!(
1047 other_user_id = %other_user_id,
1048 "Created new session - Welcome message needs to be sent"
1049 );
1050 let key_type = StorageKeyType::PendingWelcome.as_str();
1051 let welcome_data =
1052 serde_json::to_vec(&welcome).map_err(|e| MlsError::Serialization(e.to_string()))?;
1053 self.storage.store(key_type, other_user_id, &welcome_data)?;
1054 }
1055
1056 let signature_keys = self.get_signer()?;
1057 self.session_manager
1058 .encrypt_message(other_user_id, plaintext, &signature_keys)
1059 }
1060
1061 pub fn encrypt_for_existing_session(
1068 &self,
1069 other_user_id: &str,
1070 plaintext: &[u8],
1071 ) -> Result<EncryptedMessage> {
1072 let signature_keys = self.get_signer()?;
1073 self.session_manager
1074 .encrypt_message(other_user_id, plaintext, &signature_keys)
1075 }
1076
1077 pub fn get_pending_welcome(&self, other_user_id: &str) -> Result<Option<WelcomeMessage>> {
1079 let key_type = StorageKeyType::PendingWelcome.as_str();
1080 match self.storage.load(key_type, other_user_id)? {
1081 Some(data) => {
1082 let welcome: WelcomeMessage = serde_json::from_slice(&data)
1083 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1084 Ok(Some(welcome))
1085 }
1086 None => Ok(None),
1087 }
1088 }
1089
1090 pub fn clear_pending_welcome(&self, other_user_id: &str) -> Result<()> {
1092 let key_type = StorageKeyType::PendingWelcome.as_str();
1093 self.storage.delete(key_type, other_user_id)?;
1094 Ok(())
1095 }
1096
1097 pub fn decrypt_from_user(
1104 &self,
1105 encrypted: &EncryptedMessage,
1106 claimed_sender: &str,
1107 ) -> Result<Option<Vec<u8>>> {
1108 self.session_manager
1109 .decrypt_message(encrypted, claimed_sender)
1110 }
1111
1112 pub fn list_sessions(&self) -> Result<Vec<String>> {
1114 self.session_manager.list_sessions()
1115 }
1116
1117 pub fn delete_session(&self, other_user_id: &str) -> Result<()> {
1119 self.session_manager.delete_session(other_user_id)
1120 }
1121
1122 pub fn create_group(&self, group_name: &str) -> Result<GroupInfo> {
1128 let group_id = GroupId::new(format!("group:{}", Uuid::new_v4()))?;
1129 let credential = self.get_credential()?;
1130 let signature_keys = self.get_signer()?;
1131
1132 let group = self
1133 .group_manager
1134 .create_group(&group_id, &credential, &signature_keys)?;
1135
1136 let metadata = GroupMetadata::new_with_creator(Some(group_name.to_string()), &self.user_id);
1138 self.save_group_metadata(&group_id, &metadata)?;
1139
1140 let mut info = self.group_manager.get_group_info(&group, &group_id);
1141 info.name = metadata.name;
1142 info.created_at_ms = metadata.created_at_ms;
1143 info.last_activity_ms = metadata.last_activity_ms;
1144
1145 info!(group_id = %group_id, name = %group_name, "Created new group");
1146 Ok(info)
1147 }
1148
1149 pub fn add_group_member(
1171 &self,
1172 group_id: &GroupId,
1173 invitee_user_id: &str,
1174 member_key_package: &[u8],
1175 ) -> Result<(WelcomeMessage, EncryptedMessage)> {
1176 let key_package = self.validate_peer_key_package(member_key_package)?;
1177
1178 Self::verify_credential_identity(&key_package, invitee_user_id)?;
1179 Self::verify_address_binding(&key_package, invitee_user_id)?;
1180 Self::verify_lifetime_bound(&key_package)?;
1181
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 (commit, welcome) =
1189 self.group_manager
1190 .add_member(&mut group, key_package, &signature_keys)?;
1191
1192 self.group_manager.save_group(group_id, &group)?;
1193
1194 let welcome_bytes = welcome
1195 .tls_serialize_detached()
1196 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1197
1198 let commit_bytes = commit
1199 .tls_serialize_detached()
1200 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1201
1202 let group_name = self.load_group_metadata(group_id)?.and_then(|m| m.name);
1204
1205 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
1206
1207 let welcome_msg = WelcomeMessage {
1208 group_id: group_id.clone(),
1209 welcome_data: welcome_bytes,
1210 inviter_id: self.user_id.clone(),
1211 group_name,
1212 timestamp_ms: now_ms,
1213 };
1214
1215 let commit_msg = EncryptedMessage {
1216 group_id: group_id.clone(),
1217 message_type: MlsMessageType::Commit,
1218 epoch: group.epoch().as_u64(),
1219 ciphertext: commit_bytes,
1220 sender_id: self.user_id.clone(),
1221 timestamp_ms: now_ms,
1222 };
1223
1224 Ok((welcome_msg, commit_msg))
1225 }
1226
1227 pub fn remove_group_member(
1254 &self,
1255 group_id: &GroupId,
1256 member_id: &str,
1257 ) -> Result<EncryptedMessage> {
1258 let mut group = self
1259 .group_manager
1260 .load_group(group_id)?
1261 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1262
1263 let member_indices: Vec<LeafNodeIndex> = group
1264 .members()
1265 .filter(|m| m.credential.serialized_content() == member_id.as_bytes())
1266 .map(|m| m.index)
1267 .collect();
1268
1269 if member_indices.is_empty() {
1270 return Err(MlsError::UserNotInGroup(member_id.to_string()));
1271 }
1272
1273 let signature_keys = self.get_signer()?;
1274 let commit =
1275 self.group_manager
1276 .remove_member(&mut group, &member_indices, &signature_keys)?;
1277
1278 self.group_manager.save_group(group_id, &group)?;
1279
1280 let ciphertext = commit
1281 .tls_serialize_detached()
1282 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1283
1284 Ok(EncryptedMessage {
1285 group_id: group_id.clone(),
1286 message_type: MlsMessageType::Commit,
1287 epoch: group.epoch().as_u64(),
1288 ciphertext,
1289 sender_id: self.user_id.clone(),
1290 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1291 })
1292 }
1293
1294 pub fn leave_group(&self, group_id: &GroupId) -> Result<()> {
1296 self.group_manager.delete_group(group_id)?;
1297 info!(group_id = %group_id, "Left group");
1298 Ok(())
1299 }
1300
1301 pub fn encrypt_for_group(
1303 &self,
1304 group_id: &GroupId,
1305 plaintext: &[u8],
1306 ) -> Result<EncryptedMessage> {
1307 let mut group = self
1308 .group_manager
1309 .load_group(group_id)?
1310 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1311
1312 let signature_keys = self.get_signer()?;
1313 let mls_message =
1314 self.group_manager
1315 .encrypt_message(&mut group, plaintext, &signature_keys)?;
1316
1317 self.group_manager.save_group(group_id, &group)?;
1318
1319 let ciphertext = mls_message
1320 .tls_serialize_detached()
1321 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1322
1323 Ok(EncryptedMessage {
1324 group_id: group_id.clone(),
1325 message_type: MlsMessageType::Application,
1326 epoch: group.epoch().as_u64(),
1327 ciphertext,
1328 sender_id: self.user_id.clone(),
1329 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1330 })
1331 }
1332
1333 pub fn decrypt_from_group(
1341 &self,
1342 encrypted: &EncryptedMessage,
1343 claimed_sender: &str,
1344 ) -> Result<Option<Vec<u8>>> {
1345 let mut group = self
1346 .group_manager
1347 .load_group(&encrypted.group_id)?
1348 .ok_or_else(|| MlsError::GroupNotFound(encrypted.group_id.to_string()))?;
1349
1350 let mls_message = MlsMessageIn::tls_deserialize_exact(&encrypted.ciphertext)
1351 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1352
1353 let result = self.group_manager.decrypt_message(
1354 &mut group,
1355 &encrypted.group_id,
1356 mls_message,
1357 claimed_sender,
1358 )?;
1359
1360 self.group_manager.save_group(&encrypted.group_id, &group)?;
1361
1362 Ok(result)
1363 }
1364
1365 pub fn join_group(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1367 if welcome.group_id.as_str().starts_with("session:") {
1379 return Err(MlsError::ReservedSessionNamespace {
1380 group_id: welcome.group_id.to_string(),
1381 });
1382 }
1383
1384 let mls_msg = MlsMessageIn::tls_deserialize_exact(&welcome.welcome_data)
1385 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1386
1387 let welcome_msg = match mls_msg.extract() {
1388 MlsMessageBodyIn::Welcome(w) => w,
1389 _ => {
1390 return Err(MlsError::WelcomeProcessing(
1391 "Not a Welcome message".to_string(),
1392 ))
1393 }
1394 };
1395
1396 let group = self
1397 .group_manager
1398 .join_group(welcome_msg, &welcome.group_id)?;
1399 let mut info = self.group_manager.get_group_info(&group, &welcome.group_id);
1400 info.name = welcome.group_name.clone();
1401
1402 info!(group_id = %welcome.group_id, "Joined group");
1403 Ok(info)
1404 }
1405
1406 pub fn list_groups(&self) -> Result<Vec<GroupId>> {
1408 let all_groups = self.group_manager.list_groups()?;
1409 Ok(all_groups
1410 .into_iter()
1411 .filter(|g| !g.as_str().starts_with("session:"))
1412 .collect())
1413 }
1414
1415 pub fn get_group_info(&self, group_id: &GroupId) -> Result<Option<GroupInfo>> {
1417 let group = match self.group_manager.load_group(group_id)? {
1418 Some(g) => g,
1419 None => return Ok(None),
1420 };
1421
1422 let mut info = self.group_manager.get_group_info(&group, group_id);
1423
1424 if let Some(metadata) = self.load_group_metadata(group_id)? {
1426 info.name = metadata.name;
1427 info.created_at_ms = metadata.created_at_ms;
1428 info.last_activity_ms = metadata.last_activity_ms;
1429 }
1430
1431 Ok(Some(info))
1432 }
1433
1434 pub fn has_group(&self, group_id: &GroupId) -> Result<bool> {
1443 Ok(self.group_manager.load_group(group_id)?.is_some())
1444 }
1445
1446 pub fn set_group_name(&self, group_id: &GroupId, name: &str) -> Result<()> {
1448 let mut metadata = self
1449 .load_group_metadata(group_id)?
1450 .unwrap_or_else(|| GroupMetadata::new(None));
1451 metadata.name = Some(name.to_string());
1452 metadata.touch();
1453 self.save_group_metadata(group_id, &metadata)
1454 }
1455
1456 pub fn get_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1458 self.load_group_metadata(group_id)
1459 }
1460
1461 pub fn set_group_custom_metadata(
1463 &self,
1464 group_id: &GroupId,
1465 key: &str,
1466 value: &str,
1467 ) -> Result<()> {
1468 let mut metadata = self
1469 .load_group_metadata(group_id)?
1470 .unwrap_or_else(|| GroupMetadata::new(None));
1471 metadata.custom.insert(key.to_string(), value.to_string());
1472 metadata.touch();
1473 self.save_group_metadata(group_id, &metadata)
1474 }
1475
1476 pub fn remove_group_custom_metadata(&self, group_id: &GroupId, key: &str) -> Result<()> {
1478 let mut metadata = self
1479 .load_group_metadata(group_id)?
1480 .unwrap_or_else(|| GroupMetadata::new(None));
1481 metadata.custom.remove(key);
1482 metadata.touch();
1483 self.save_group_metadata(group_id, &metadata)
1484 }
1485
1486 pub fn set_member_role(
1488 &self,
1489 group_id: &GroupId,
1490 user_id: &str,
1491 role: GroupRole,
1492 ) -> Result<()> {
1493 let mut metadata = self
1494 .load_group_metadata(group_id)?
1495 .unwrap_or_else(|| GroupMetadata::new(None));
1496 metadata.set_role(user_id, role);
1497 metadata.touch();
1498 self.save_group_metadata(group_id, &metadata)
1499 }
1500
1501 pub fn set_enforce_admin_commits(&mut self, enforce: bool) {
1510 self.group_manager.set_enforce_admin_commits(enforce);
1511 }
1512
1513 pub fn set_group_creator(&self, group_id: &GroupId, creator_id: &str) -> Result<()> {
1527 let mut metadata = self
1528 .load_group_metadata(group_id)?
1529 .unwrap_or_else(|| GroupMetadata::new(None));
1530 if metadata.created_by.is_some() {
1531 return Ok(());
1532 }
1533 metadata.created_by = Some(creator_id.to_string());
1534 metadata.touch();
1535 self.save_group_metadata(group_id, &metadata)
1536 }
1537
1538 pub fn remove_member_role(&self, group_id: &GroupId, user_id: &str) -> Result<()> {
1540 let mut metadata = self
1541 .load_group_metadata(group_id)?
1542 .unwrap_or_else(|| GroupMetadata::new(None));
1543 metadata.remove_role(user_id);
1544 metadata.touch();
1545 self.save_group_metadata(group_id, &metadata)
1546 }
1547
1548 pub fn decrypt(
1558 &self,
1559 encrypted: &EncryptedMessage,
1560 claimed_sender: &str,
1561 ) -> Result<Option<Vec<u8>>> {
1562 if encrypted.group_id.as_str().starts_with("session:") {
1563 self.decrypt_from_user(encrypted, claimed_sender)
1564 } else {
1565 self.decrypt_from_group(encrypted, claimed_sender)
1566 }
1567 }
1568
1569 pub fn process_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1571 if welcome.group_id.as_str().starts_with("session:") {
1572 self.join_session(welcome)
1573 } else {
1574 self.join_group(welcome)
1575 }
1576 }
1577}
1578
1579impl MlsManager {
1580 fn load_stored_key_package(&self, package_id: &str) -> Result<Option<KeyPackageBundle>> {
1586 let key_type = StorageKeyType::KeyPackage.as_str();
1587 let data = match self.storage.load(key_type, package_id)? {
1588 Some(data) => data,
1589 None => return Ok(None),
1590 };
1591
1592 let mut bundle = match serde_json::from_slice::<KeyPackageBundle>(&data) {
1593 Ok(bundle) => bundle,
1594 Err(_) => {
1595 let legacy_bundle = KeyPackageBundle::new(
1596 package_id.to_string(),
1597 self.user_id.clone(),
1598 data,
1599 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
1600 );
1601 let serialized = serde_json::to_vec(&legacy_bundle)
1602 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1603 self.storage.store(key_type, package_id, &serialized)?;
1604 legacy_bundle
1605 }
1606 };
1607
1608 if bundle.is_expired() {
1609 if bundle.expired_past_grace(KEY_PACKAGE_PURGE_GRACE_SECS) {
1616 self.purge_key_package_material(package_id, &bundle)?;
1617 }
1618 return Ok(None);
1619 }
1620
1621 let needs_backfill = bundle.provider_hash_ref.is_none();
1627 let Some(hash_ref) = self.bundle_hash_ref(&bundle) else {
1628 warn!(
1629 package_id = %package_id,
1630 "Key package bytes no longer parse as a key package, pruning stale entry"
1631 );
1632 self.storage.delete(key_type, package_id)?;
1633 return Ok(None);
1634 };
1635 if !self.provider_has_init_key(&hash_ref) {
1636 warn!(
1637 package_id = %package_id,
1638 "Key package private key no longer in provider storage, pruning stale entry"
1639 );
1640 self.storage.delete(key_type, package_id)?;
1641 return Ok(None);
1642 }
1643
1644 if needs_backfill {
1645 if let Ok(bytes) = hash_ref.tls_serialize_detached() {
1646 bundle.provider_hash_ref = Some(bytes);
1647 let serialized = serde_json::to_vec(&bundle)
1648 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1649 self.storage.store(key_type, package_id, &serialized)?;
1650 }
1651 }
1652
1653 Ok(Some(bundle))
1654 }
1655
1656 fn purge_key_package_material(
1671 &self,
1672 package_id: &str,
1673 bundle: &KeyPackageBundle,
1674 ) -> Result<()> {
1675 if let Some(hash_ref) = self.bundle_hash_ref(bundle) {
1676 use openmls_traits::storage::StorageProvider;
1677 if let Err(e) = self.provider.storage().delete_key_package(&hash_ref) {
1678 warn!(
1679 package_id = %package_id,
1680 error = %e,
1681 "Failed to delete a key package's private init key; keeping the record to retry"
1682 );
1683 return Ok(());
1684 }
1685 }
1686
1687 self.storage
1688 .delete(StorageKeyType::KeyPackage.as_str(), package_id)?;
1689 debug!(package_id = %package_id, "Purged key package and its init key");
1690 Ok(())
1691 }
1692
1693 fn key_package_hash_ref(&self, key_package_data: &[u8]) -> Option<KeyPackageRef> {
1700 let kp_in = KeyPackageIn::tls_deserialize_exact(key_package_data).ok()?;
1701 let kp: KeyPackage = kp_in
1702 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
1703 .ok()?;
1704 kp.hash_ref(self.provider.crypto()).ok()
1705 }
1706
1707 fn bundle_hash_ref(&self, bundle: &KeyPackageBundle) -> Option<KeyPackageRef> {
1711 bundle
1712 .provider_hash_ref
1713 .as_deref()
1714 .and_then(|bytes| KeyPackageRef::tls_deserialize_exact(bytes).ok())
1715 .or_else(|| self.key_package_hash_ref(&bundle.key_package_data))
1716 }
1717
1718 fn provider_has_init_key(&self, hash_ref: &KeyPackageRef) -> bool {
1722 use openmls_traits::storage::StorageProvider;
1723 let found: std::result::Result<Option<openmls::key_packages::KeyPackageBundle>, _> =
1724 self.provider.storage().key_package(hash_ref);
1725 matches!(found, Ok(Some(_)))
1726 }
1727
1728 #[cfg(test)]
1733 fn is_key_package_usable(&self, key_package_data: &[u8]) -> bool {
1734 self.key_package_hash_ref(key_package_data)
1735 .map(|hash_ref| self.provider_has_init_key(&hash_ref))
1736 .unwrap_or(false)
1737 }
1738
1739 fn load_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1741 let key_type = StorageKeyType::GroupMetadata.as_str();
1742 match self.storage.load(key_type, group_id.as_str())? {
1743 Some(data) => {
1744 let mut metadata: GroupMetadata = serde_json::from_slice(&data)
1745 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1746 if metadata.roles.is_empty()
1748 && metadata
1749 .custom
1750 .keys()
1751 .any(|k| k.starts_with(GroupMetadata::LEGACY_ROLE_KEY_PREFIX))
1752 {
1753 metadata.migrate_legacy_roles();
1754 if let Err(e) = self.save_group_metadata(group_id, &metadata) {
1756 warn!(group_id = %group_id.as_str(), error = %e, "Failed to persist legacy role migration");
1757 }
1758 }
1759 Ok(Some(metadata))
1760 }
1761 None => Ok(None),
1762 }
1763 }
1764
1765 fn save_group_metadata(&self, group_id: &GroupId, metadata: &GroupMetadata) -> Result<()> {
1767 let key_type = StorageKeyType::GroupMetadata.as_str();
1768 let data =
1769 serde_json::to_vec(metadata).map_err(|e| MlsError::Serialization(e.to_string()))?;
1770 self.storage.store(key_type, group_id.as_str(), &data)?;
1771 Ok(())
1772 }
1773}
1774
1775impl MlsManager {
1780 pub fn update_keys(&self, group_id: &GroupId) -> Result<EncryptedMessage> {
1789 use openmls::treesync::LeafNodeParameters;
1790
1791 let mut group = self
1792 .group_manager
1793 .load_group(group_id)?
1794 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1795
1796 let signature_keys = self.get_signer()?;
1797
1798 let bundle = group
1799 .self_update(
1800 &self.provider,
1801 &signature_keys,
1802 LeafNodeParameters::default(),
1803 )
1804 .map_err(|e| MlsError::OpenMls(format!("Self-update failed: {}", e)))?;
1805
1806 let (commit, _welcome, _group_info) = bundle.into_contents();
1807
1808 group
1809 .merge_pending_commit(&self.provider)
1810 .map_err(|e| MlsError::OpenMls(format!("Failed to merge self-update commit: {}", e)))?;
1811
1812 self.group_manager.save_group(group_id, &group)?;
1813
1814 if let Some(mut metadata) = self.load_group_metadata(group_id)? {
1816 metadata.touch();
1817 self.save_group_metadata(group_id, &metadata)?;
1818 }
1819
1820 let ciphertext = commit
1821 .tls_serialize_detached()
1822 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1823
1824 debug!(group_id = %group_id, epoch = %group.epoch().as_u64(), "Updated group keys");
1825
1826 Ok(EncryptedMessage {
1827 group_id: group_id.clone(),
1828 message_type: MlsMessageType::Commit,
1829 epoch: group.epoch().as_u64(),
1830 ciphertext,
1831 sender_id: self.user_id.clone(),
1832 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1833 })
1834 }
1835
1836 pub fn ensure_min_key_packages(&self, min: usize) -> Result<usize> {
1861 let min = min.min(MAX_PUSH_KEY_PACKAGES);
1862 let key_type = StorageKeyType::KeyPackage.as_str();
1863 let package_ids = self.storage.list_keys(key_type)?;
1864
1865 let mut valid_count = 0;
1867 for package_id in &package_ids {
1868 if self
1869 .load_stored_key_package(package_id)?
1870 .is_some_and(|bundle| !bundle.withheld_from_hand_out())
1871 {
1872 valid_count += 1;
1873 }
1874 }
1875
1876 let to_generate = min.saturating_sub(valid_count);
1878 for _ in 0..to_generate {
1879 self.generate_key_package()?;
1880 valid_count += 1;
1881 }
1882
1883 debug!(
1884 valid_count = valid_count,
1885 generated = to_generate,
1886 "Ensured minimum key packages"
1887 );
1888
1889 Ok(valid_count)
1890 }
1891
1892 pub fn count_valid_key_packages(&self) -> Result<usize> {
1899 let key_type = StorageKeyType::KeyPackage.as_str();
1900 let package_ids = self.storage.list_keys(key_type)?;
1901
1902 let mut count = 0;
1903 for package_id in package_ids {
1904 if self.load_stored_key_package(&package_id)?.is_some() {
1905 count += 1;
1906 }
1907 }
1908
1909 Ok(count)
1910 }
1911
1912 pub fn get_identity_public_key(&self) -> Result<Vec<u8>> {
1921 let credential = self.get_credential()?;
1922 Ok(credential.signature_key.as_slice().to_vec())
1923 }
1924
1925 pub fn sign_data(&self, data: &[u8]) -> Result<Vec<u8>> {
1938 use openmls_traits::signatures::Signer;
1939
1940 let signer = self.get_signer()?;
1941 let signature = signer
1942 .sign(data)
1943 .map_err(|e| MlsError::Signing(format!("Failed to sign data: {:?}", e)))?;
1944 Ok(signature.as_slice().to_vec())
1945 }
1946
1947 pub fn verify_signature(public_key: &[u8], data: &[u8], signature: &[u8]) -> Result<bool> {
1959 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
1960
1961 let verifying_key = VerifyingKey::try_from(public_key).map_err(|e| {
1963 MlsError::InvalidPublicKey(format!("Invalid Ed25519 public key: {}", e))
1964 })?;
1965
1966 let sig = Signature::try_from(signature).map_err(|e| {
1968 MlsError::VerificationFailed(format!("Invalid signature format: {}", e))
1969 })?;
1970
1971 match verifying_key.verify(data, &sig) {
1973 Ok(()) => Ok(true),
1974 Err(_) => Ok(false),
1975 }
1976 }
1977
1978 pub fn derive_address(public_key: &[u8]) -> Result<Address> {
2004 offline_protocol_sealed::derive_address(public_key).map_err(MlsError::from)
2005 }
2006
2007 #[deprecated(
2027 note = "use derive_address; this returns the self-certifying off1… address, no longer base58"
2028 )]
2029 pub fn derive_user_id_from_public_key(public_key: &[u8]) -> String {
2030 use sha2::{Digest, Sha256};
2031
2032 let hash = Sha256::digest(public_key);
2033 let mut truncated = [0u8; Address::HASH_LEN];
2034 truncated.copy_from_slice(&hash[..Address::HASH_LEN]);
2035 Address::from_hash_bytes(truncated).to_string()
2036 }
2037}
2038
2039#[cfg(feature = "test-utils")]
2048impl MlsManager {
2049 pub fn seat_forged_leaf_for_testing(
2063 &self,
2064 group_id: &GroupId,
2065 impersonated: &str,
2066 ) -> Result<Vec<u8>> {
2067 let forged_storage: Arc<dyn MlsStorage> = Arc::new(crate::storage::InMemoryStorage::new());
2068 let forged_provider = MlsProvider::new(MlsStorageAdapter::new(forged_storage));
2069 let forged_keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
2070 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
2071 forged_keys
2072 .store(forged_provider.storage())
2073 .map_err(|e| MlsError::CryptoGeneration(format!("storing forged key: {:?}", e)))?;
2074
2075 let credential = CredentialWithKey {
2076 credential: Credential::new(CredentialType::Basic, impersonated.as_bytes().to_vec()),
2077 signature_key: forged_keys.public().into(),
2078 };
2079 let key_package = KeyPackage::builder()
2080 .build(
2081 DEFAULT_CIPHERSUITE,
2082 &forged_provider,
2083 &forged_keys,
2084 credential,
2085 )
2086 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?
2087 .key_package()
2088 .tls_serialize_detached()
2089 .map_err(|e| MlsError::Serialization(e.to_string()))?;
2090
2091 let validated = KeyPackageIn::tls_deserialize_exact(&key_package)
2092 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?
2093 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
2094 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
2095
2096 let mut group = self
2097 .group_manager
2098 .load_group(group_id)?
2099 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
2100 let signer = self.get_signer()?;
2101 let (commit, _welcome) = self
2102 .group_manager
2103 .add_member(&mut group, validated, &signer)?;
2104 self.group_manager.save_group(group_id, &group)?;
2105
2106 commit
2107 .tls_serialize_detached()
2108 .map_err(|e| MlsError::Serialization(e.to_string()))
2109 }
2110
2111 pub fn key_package_with_window_for_testing(
2119 claimed: &str,
2120 not_before: u64,
2121 not_after: u64,
2122 ) -> Result<Vec<u8>> {
2123 let storage: Arc<dyn MlsStorage> = Arc::new(crate::storage::InMemoryStorage::new());
2124 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
2125 let keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
2126 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
2127 keys.store(provider.storage())
2128 .map_err(|e| MlsError::CryptoGeneration(format!("storing key: {:?}", e)))?;
2129 let credential = CredentialWithKey {
2130 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
2131 signature_key: keys.public().into(),
2132 };
2133 KeyPackage::builder()
2134 .key_package_lifetime(Lifetime::init(not_before, not_after))
2135 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
2136 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?
2137 .key_package()
2138 .tls_serialize_detached()
2139 .map_err(|e| MlsError::Serialization(e.to_string()))
2140 }
2141}
2142
2143pub fn is_mls_framed(bytes: &[u8]) -> bool {
2152 MlsMessageIn::tls_deserialize_exact(bytes).is_ok()
2153}
2154
2155#[cfg(test)]
2156mod tests {
2157 use super::*;
2158 use crate::storage::InMemoryStorage;
2159
2160 fn create_test_manager(label: &str) -> MlsManager {
2169 create_addressed_manager(label).0
2170 }
2171
2172 fn addr(label: &str) -> String {
2174 test_identity(label).1.to_string()
2175 }
2176
2177 fn slot(a: &str, b: &str) -> GroupId {
2183 GroupId::for_session(&addr(a), &addr(b)).unwrap()
2184 }
2185
2186 pub(crate) fn test_identity(label: &str) -> (SignatureKeyPair, Address) {
2194 use sha2::{Digest, Sha256};
2195
2196 let seed: [u8; 32] = Sha256::digest(label.as_bytes()).into();
2197 let signing = ed25519_dalek::SigningKey::from_bytes(&seed);
2198 let public = signing.verifying_key().to_bytes().to_vec();
2199 let address = MlsManager::derive_address(&public).unwrap();
2200 let keys = SignatureKeyPair::from_raw(
2201 DEFAULT_CIPHERSUITE.signature_algorithm(),
2202 signing.to_bytes().to_vec(),
2203 public,
2204 );
2205 (keys, address)
2206 }
2207
2208 pub(crate) fn seed_identity(storage: &Arc<dyn MlsStorage>, keys: &SignatureKeyPair) {
2211 storage
2212 .store(
2213 StorageKeyType::Identity.as_str(),
2214 "key_pair",
2215 &serde_json::to_vec(keys).unwrap(),
2216 )
2217 .unwrap();
2218 }
2219
2220 fn create_addressed_manager(label: &str) -> (MlsManager, Address) {
2223 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2224 let (keys, address) = test_identity(label);
2225 seed_identity(&storage, &keys);
2226 let manager = MlsManager::new(address.to_string(), storage).unwrap();
2227 (manager, address)
2228 }
2229
2230 fn create_test_manager_with_storage(user_id: &str) -> (MlsManager, Arc<InMemoryStorage>) {
2234 let storage = Arc::new(InMemoryStorage::new());
2235 let manager = MlsManager::new(user_id, storage.clone()).unwrap();
2236 (manager, storage)
2237 }
2238
2239 fn expire_package(storage: &InMemoryStorage, package_id: &str, secs_ago: u64) {
2241 let key_type = StorageKeyType::KeyPackage.as_str();
2242 let data = storage
2243 .load(key_type, package_id)
2244 .unwrap()
2245 .expect("package to age is not in storage");
2246 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2247 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
2248 bundle.expires_at_ms = now_ms - (secs_ago * 1000);
2249 storage
2250 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2251 .unwrap();
2252 }
2253
2254 fn init_key_present(manager: &MlsManager, bundle: &KeyPackageBundle) -> bool {
2256 manager.is_key_package_usable(&bundle.key_package_data)
2257 }
2258
2259 fn strip_cached_ref(storage: &InMemoryStorage, package_id: &str) {
2262 let key_type = StorageKeyType::KeyPackage.as_str();
2263 let data = storage
2264 .load(key_type, package_id)
2265 .unwrap()
2266 .expect("package to strip is not in storage");
2267 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2268 bundle.provider_hash_ref = None;
2269 storage
2270 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2271 .unwrap();
2272 }
2273
2274 #[test]
2279 fn test_minted_package_carries_its_provider_hash_ref() {
2280 let manager = create_test_manager("alice");
2281 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2282
2283 let cached = bundle
2284 .provider_hash_ref
2285 .as_deref()
2286 .expect("mint must stamp the provider hash ref");
2287 let derived = manager
2288 .key_package_hash_ref(&bundle.key_package_data)
2289 .expect("freshly minted package bytes must parse")
2290 .tls_serialize_detached()
2291 .unwrap();
2292 assert_eq!(
2293 cached,
2294 &derived[..],
2295 "cached ref disagrees with the one derived from the package bytes"
2296 );
2297 }
2298
2299 #[test]
2302 fn test_record_without_cached_ref_is_backfilled_on_first_load() {
2303 let (manager, storage) = create_test_manager_with_storage("alice");
2304 let minted = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2305 strip_cached_ref(&storage, &minted.package_id);
2306
2307 let loaded = manager
2308 .key_package_by_id(&minted.package_id)
2309 .unwrap()
2310 .expect("a record without a cached ref must still load");
2311 assert_eq!(
2312 loaded.provider_hash_ref, minted.provider_hash_ref,
2313 "load must hand back a bundle stamped with the same ref the mint computed"
2314 );
2315
2316 let stored: KeyPackageBundle = serde_json::from_slice(
2317 &storage
2318 .load(StorageKeyType::KeyPackage.as_str(), &minted.package_id)
2319 .unwrap()
2320 .unwrap(),
2321 )
2322 .unwrap();
2323 assert_eq!(
2324 stored.provider_hash_ref, minted.provider_hash_ref,
2325 "the backfilled ref must be persisted, not only returned"
2326 );
2327 }
2328
2329 #[test]
2332 fn test_purge_destroys_init_key_for_a_record_without_cached_ref() {
2333 let (manager, storage) = create_test_manager_with_storage("alice");
2334 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2335 strip_cached_ref(&storage, &bundle.package_id);
2336 expire_package(
2337 &storage,
2338 &bundle.package_id,
2339 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2340 );
2341
2342 assert!(manager
2343 .key_package_by_id(&bundle.package_id)
2344 .unwrap()
2345 .is_none());
2346 assert!(
2347 !init_key_present(&manager, &bundle),
2348 "the private init key outlived a pre-cache package"
2349 );
2350 }
2351
2352 #[test]
2357 fn test_expired_package_is_withdrawn_but_its_init_key_survives_the_grace_window() {
2358 let (manager, storage) = create_test_manager_with_storage("alice");
2359 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2360
2361 expire_package(&storage, &bundle.package_id, 60);
2362
2363 assert!(
2364 manager
2365 .key_package_by_id(&bundle.package_id)
2366 .unwrap()
2367 .is_none(),
2368 "an expired package must not be handed out"
2369 );
2370 assert!(
2371 init_key_present(&manager, &bundle),
2372 "the init key was destroyed on the expiry edge, breaking a late Welcome"
2373 );
2374
2375 let replacement = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2377 assert_ne!(replacement.package_id, bundle.package_id);
2378 }
2379
2380 #[test]
2385 fn test_package_past_the_grace_window_has_its_init_key_destroyed() {
2386 let (manager, storage) = create_test_manager_with_storage("alice");
2387 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2388 assert!(init_key_present(&manager, &bundle));
2389
2390 expire_package(
2391 &storage,
2392 &bundle.package_id,
2393 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2394 );
2395
2396 assert!(manager
2398 .key_package_by_id(&bundle.package_id)
2399 .unwrap()
2400 .is_none());
2401
2402 assert!(
2403 !init_key_present(&manager, &bundle),
2404 "the private init key outlived its package"
2405 );
2406 assert!(
2407 storage
2408 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2409 .unwrap()
2410 .is_none(),
2411 "the bundle record outlived its key material"
2412 );
2413 }
2414
2415 #[test]
2419 fn test_delete_key_package_destroys_the_init_key() {
2420 let manager = create_test_manager("alice");
2421 let bundle = manager.generate_publication_key_package().unwrap();
2422 assert!(init_key_present(&manager, &bundle));
2423
2424 manager.delete_key_package(&bundle.package_id).unwrap();
2425
2426 assert!(
2427 !init_key_present(&manager, &bundle),
2428 "delete_key_package left the private init key in provider storage"
2429 );
2430 }
2431
2432 #[test]
2437 fn test_delete_key_package_destroys_the_init_key_of_a_legacy_record() {
2438 let (manager, storage) = create_test_manager_with_storage("alice");
2439 let bundle = manager.generate_publication_key_package().unwrap();
2440 storage
2442 .store(
2443 StorageKeyType::KeyPackage.as_str(),
2444 &bundle.package_id,
2445 &bundle.key_package_data,
2446 )
2447 .unwrap();
2448
2449 manager.delete_key_package(&bundle.package_id).unwrap();
2450
2451 assert!(
2452 !init_key_present(&manager, &bundle),
2453 "a legacy record's private init key survived its deletion"
2454 );
2455 assert!(
2456 storage
2457 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2458 .unwrap()
2459 .is_none(),
2460 "the legacy record outlived its key material"
2461 );
2462 }
2463
2464 #[test]
2468 fn test_delete_key_package_removes_an_unreadable_record() {
2469 let (manager, storage) = create_test_manager_with_storage("alice");
2470 storage
2471 .store(
2472 StorageKeyType::KeyPackage.as_str(),
2473 "junk",
2474 b"not a package",
2475 )
2476 .unwrap();
2477
2478 manager.delete_key_package("junk").unwrap();
2479
2480 assert!(storage
2481 .load(StorageKeyType::KeyPackage.as_str(), "junk")
2482 .unwrap()
2483 .is_none());
2484 }
2485
2486 #[test]
2492 fn test_ensure_min_key_packages_is_capped_at_the_pool_ceiling() {
2493 let manager = create_test_manager("alice");
2494
2495 let count = manager
2496 .ensure_min_key_packages(MAX_PUSH_KEY_PACKAGES + 50)
2497 .unwrap();
2498
2499 assert_eq!(count, MAX_PUSH_KEY_PACKAGES);
2500 assert_eq!(
2501 manager.count_valid_key_packages().unwrap(),
2502 MAX_PUSH_KEY_PACKAGES,
2503 "pre-filling minted past the ceiling the push path enforces"
2504 );
2505 }
2506
2507 #[test]
2508 fn test_manager_creation() {
2509 let manager = create_test_manager("alice");
2510 assert_eq!(manager.user_id(), addr("alice"));
2511 }
2512
2513 #[test]
2514 fn test_key_package_generation() {
2515 let manager = create_test_manager("alice");
2516 let package = manager.generate_key_package().unwrap();
2517
2518 assert_eq!(package.user_id, addr("alice"));
2519 assert!(!package.key_package_data.is_empty());
2520 assert!(!package.is_expired());
2521 }
2522
2523 #[test]
2528 fn test_get_or_create_never_returns_a_publication_package() {
2529 let manager = create_test_manager("alice");
2530 let reserved = manager.generate_publication_key_package().unwrap();
2531 assert!(reserved.reserved_for_publication);
2532
2533 let pushed = manager.get_or_create_key_package().unwrap();
2534 assert_ne!(
2535 pushed.package_id, reserved.package_id,
2536 "the push path took the package a published record is standing on"
2537 );
2538 assert!(!pushed.reserved_for_publication);
2539
2540 let pushed_again = manager.get_or_create_key_package().unwrap();
2543 assert_eq!(pushed.package_id, pushed_again.package_id);
2544 }
2545
2546 #[test]
2550 fn test_key_package_by_id_reports_presence() {
2551 let manager = create_test_manager("alice");
2552 let bundle = manager.generate_publication_key_package().unwrap();
2553
2554 let found = manager.key_package_by_id(&bundle.package_id).unwrap();
2555 assert!(found.is_some());
2556 assert_eq!(found.unwrap().package_id, bundle.package_id);
2557
2558 assert!(manager
2559 .key_package_by_id("no-such-package")
2560 .unwrap()
2561 .is_none());
2562 }
2563
2564 #[test]
2567 fn test_publication_packages_are_distinct_per_slot() {
2568 let manager = create_test_manager("alice");
2569 let a = manager.generate_publication_key_package().unwrap();
2570 let b = manager.generate_publication_key_package().unwrap();
2571 assert_ne!(a.package_id, b.package_id);
2572 assert_ne!(a.key_package_data, b.key_package_data);
2573 }
2574
2575 #[test]
2576 fn test_get_or_create_key_package() {
2577 let manager = create_test_manager("alice");
2578 let pkg1 = manager.get_or_create_key_package().unwrap();
2579 let pkg2 = manager.get_or_create_key_package().unwrap();
2580 assert_eq!(pkg1.package_id, pkg2.package_id);
2584 }
2585
2586 #[test]
2596 fn test_push_path_gives_each_peer_its_own_key_package() {
2597 let manager = create_test_manager("alice");
2598
2599 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap();
2600 let for_carol = manager.take_push_key_package(&addr("carol")).unwrap();
2601
2602 assert_ne!(
2603 for_bob.bundle.package_id, for_carol.bundle.package_id,
2604 "two peers were advertised the same key package"
2605 );
2606 assert_ne!(
2607 for_bob.bundle.key_package_data, for_carol.bundle.key_package_data,
2608 "two peers were advertised the same init key"
2609 );
2610 assert!(!for_bob.pool_exhausted && !for_carol.pool_exhausted);
2611 assert_eq!(
2612 for_bob.bundle.assigned_peer.as_deref(),
2613 Some(addr("bob").as_str())
2614 );
2615 assert_eq!(
2616 for_carol.bundle.assigned_peer.as_deref(),
2617 Some(addr("carol").as_str())
2618 );
2619 }
2620
2621 #[test]
2626 fn test_push_path_reuses_one_package_for_repeat_pushes_to_a_peer() {
2627 let manager = create_test_manager("alice");
2628
2629 let first = manager.take_push_key_package(&addr("bob")).unwrap();
2630 let second = manager.take_push_key_package(&addr("bob")).unwrap();
2631 let third = manager.take_push_key_package(&addr("bob")).unwrap();
2632
2633 assert_eq!(first.bundle.package_id, second.bundle.package_id);
2634 assert_eq!(first.bundle.package_id, third.bundle.package_id);
2635 assert_eq!(
2636 manager.count_valid_key_packages().unwrap(),
2637 1,
2638 "repeat pushes to one peer minted extra key material"
2639 );
2640 }
2641
2642 #[test]
2647 fn test_push_path_mints_a_fresh_package_after_the_peer_consumed_one() {
2648 let alice = create_test_manager("alice");
2649 let bob = create_test_manager("bob");
2650
2651 let advertised = alice.take_push_key_package(&addr("bob")).unwrap().bundle;
2652 bob.import_key_package(&addr("alice"), &advertised.key_package_data)
2653 .unwrap();
2654 let welcome = bob.create_session(&addr("alice")).unwrap();
2655 alice.join_session(&welcome).unwrap();
2656
2657 let next = alice.take_push_key_package(&addr("bob")).unwrap();
2658 assert_ne!(
2659 next.bundle.package_id, advertised.package_id,
2660 "a consumed package was advertised again"
2661 );
2662 assert!(!next.pool_exhausted);
2663 }
2664
2665 #[test]
2670 fn test_two_peers_can_both_establish_sessions_after_being_pushed_to() {
2671 let alice = create_test_manager("alice");
2672 let bob = create_test_manager("bob");
2673 let carol = create_test_manager("carol");
2674
2675 for (peer, manager) in [(addr("bob"), &bob), (addr("carol"), &carol)] {
2676 let advertised = alice.take_push_key_package(&peer).unwrap().bundle;
2677 manager
2678 .import_key_package(&addr("alice"), &advertised.key_package_data)
2679 .unwrap();
2680 }
2681
2682 let from_bob = bob.create_session(&addr("alice")).unwrap();
2685 let from_carol = carol.create_session(&addr("alice")).unwrap();
2686
2687 alice.join_session(&from_bob).unwrap();
2688 alice
2689 .join_session(&from_carol)
2690 .expect("second peer's Welcome must still be processable");
2691
2692 assert!(alice.has_session(&addr("bob")).unwrap());
2693 assert!(alice.has_session(&addr("carol")).unwrap());
2694 }
2695
2696 #[test]
2699 fn test_push_path_never_takes_a_publication_package() {
2700 let manager = create_test_manager("alice");
2701 let reserved = manager.generate_publication_key_package().unwrap();
2702
2703 for peer in [addr("bob"), addr("carol"), addr("dave")] {
2704 let taken = manager.take_push_key_package(&peer).unwrap();
2705 assert_ne!(
2706 taken.bundle.package_id, reserved.package_id,
2707 "the push path took the package a published record stands on"
2708 );
2709 }
2710 }
2711
2712 #[test]
2716 fn test_get_or_create_never_returns_a_peer_assigned_package() {
2717 let manager = create_test_manager("alice");
2718 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2719
2720 let unclaimed = manager.get_or_create_key_package().unwrap();
2721 assert_ne!(unclaimed.package_id, for_bob.package_id);
2722 assert!(unclaimed.assigned_peer.is_none());
2723 }
2724
2725 fn record_present(storage: &InMemoryStorage, package_id: &str) -> bool {
2727 storage
2728 .load(StorageKeyType::KeyPackage.as_str(), package_id)
2729 .unwrap()
2730 .is_some()
2731 }
2732
2733 #[test]
2737 fn test_marking_synced_keeps_the_record_and_the_init_key() {
2738 let (mut manager, storage) = create_test_manager_with_storage("alice");
2739 let bundle = manager.generate_key_package().unwrap();
2740
2741 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2742
2743 assert!(
2744 record_present(&storage, &bundle.package_id),
2745 "marking synced deleted the record, so nothing can ever expire the key"
2746 );
2747 assert!(
2748 init_key_present(&manager, &bundle),
2749 "marking synced destroyed the init key a Welcome from the upload needs"
2750 );
2751 let reread = manager
2752 .key_package_by_id(&bundle.package_id)
2753 .unwrap()
2754 .expect("a synced package is still live");
2755 assert!(reread.synced);
2756 }
2757
2758 #[test]
2762 fn test_unclaimed_synced_package_has_its_init_key_destroyed_past_the_grace_window() {
2763 let (mut manager, storage) = create_test_manager_with_storage("alice");
2764 let bundle = manager.generate_key_package().unwrap();
2765 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2766
2767 expire_package(
2768 &storage,
2769 &bundle.package_id,
2770 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2771 );
2772 assert!(manager
2773 .key_package_by_id(&bundle.package_id)
2774 .unwrap()
2775 .is_none());
2776
2777 assert!(
2778 !init_key_present(&manager, &bundle),
2779 "a synced package's init key outlived its grace window"
2780 );
2781 assert!(!record_present(&storage, &bundle.package_id));
2782 }
2783
2784 #[test]
2787 fn test_synced_package_is_withheld_from_both_hand_out_paths() {
2788 let mut manager = create_test_manager("alice");
2789 let synced = manager.generate_key_package().unwrap();
2790 manager.mark_key_package_synced(&synced.package_id).unwrap();
2791
2792 let pushed = manager.take_push_key_package(&addr("bob")).unwrap();
2793 assert_ne!(pushed.bundle.package_id, synced.package_id);
2794 assert!(!pushed.pool_exhausted);
2795
2796 let peerless = manager.get_or_create_key_package().unwrap();
2797 assert_ne!(peerless.package_id, synced.package_id);
2798 }
2799
2800 #[test]
2804 fn test_pending_list_holds_only_packages_the_application_may_publish() {
2805 let mut manager = create_test_manager("alice");
2806 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2809 let plain = manager.generate_key_package().unwrap();
2810 let reserved = manager.generate_publication_key_package().unwrap();
2811 let synced = manager.generate_key_package().unwrap();
2812 manager.mark_key_package_synced(&synced.package_id).unwrap();
2813
2814 let pending: Vec<String> = manager
2815 .get_pending_key_packages()
2816 .unwrap()
2817 .into_iter()
2818 .map(|b| b.package_id)
2819 .collect();
2820
2821 assert!(pending.contains(&plain.package_id));
2822 assert!(
2823 !pending.contains(&reserved.package_id),
2824 "lists a slot package"
2825 );
2826 assert!(
2827 !pending.contains(&for_bob.package_id),
2828 "lists a peer's package"
2829 );
2830 assert!(
2831 !pending.contains(&synced.package_id),
2832 "lists a synced package"
2833 );
2834 }
2835
2836 #[test]
2840 fn test_ensure_min_does_not_count_a_synced_package_toward_the_pool() {
2841 let mut manager = create_test_manager("alice");
2842 let synced = manager.generate_key_package().unwrap();
2843 manager.mark_key_package_synced(&synced.package_id).unwrap();
2844
2845 assert_eq!(manager.ensure_min_key_packages(1).unwrap(), 1);
2846 assert_eq!(
2847 manager.count_valid_key_packages().unwrap(),
2848 2,
2849 "a replacement was minted beside the synced package"
2850 );
2851 }
2852
2853 #[test]
2856 fn test_welcome_built_against_a_synced_package_still_opens() {
2857 let mut alice = create_test_manager("alice");
2858 let bob = create_test_manager("bob");
2859 let uploaded = alice.generate_key_package().unwrap();
2860 alice.mark_key_package_synced(&uploaded.package_id).unwrap();
2861
2862 bob.import_key_package(&addr("alice"), &uploaded.key_package_data)
2863 .unwrap();
2864 let welcome = bob.create_session(&addr("alice")).unwrap();
2865
2866 alice
2867 .join_session(&welcome)
2868 .expect("a Welcome against a synced package must open");
2869 assert!(alice.has_session(&addr("bob")).unwrap());
2870 }
2871
2872 #[test]
2875 fn test_marking_a_legacy_record_upgrades_and_marks_it() {
2876 let (mut manager, storage) = create_test_manager_with_storage("alice");
2877 let bundle = manager.generate_key_package().unwrap();
2878 storage
2879 .store(
2880 StorageKeyType::KeyPackage.as_str(),
2881 &bundle.package_id,
2882 &bundle.key_package_data,
2883 )
2884 .unwrap();
2885
2886 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2887
2888 let reread = manager
2889 .key_package_by_id(&bundle.package_id)
2890 .unwrap()
2891 .expect("the legacy package is still live");
2892 assert!(reread.synced);
2893 assert!(init_key_present(&manager, &bundle));
2894 }
2895
2896 #[test]
2899 fn test_marking_an_unknown_or_consumed_package_is_a_no_op() {
2900 let mut alice = create_test_manager("alice");
2901 alice.mark_key_package_synced("no-such-package").unwrap();
2902
2903 let bob = create_test_manager("bob");
2904 let advertised = alice.take_push_key_package(&addr("bob")).unwrap().bundle;
2905 bob.import_key_package(&addr("alice"), &advertised.key_package_data)
2906 .unwrap();
2907 let welcome = bob.create_session(&addr("alice")).unwrap();
2908 alice.join_session(&welcome).unwrap();
2909
2910 alice
2911 .mark_key_package_synced(&advertised.package_id)
2912 .unwrap();
2913 assert!(alice
2914 .key_package_by_id(&advertised.package_id)
2915 .unwrap()
2916 .is_none());
2917 }
2918
2919 #[test]
2934 fn only_the_known_sites_delete_a_key_package_record_alone() {
2935 let allowed = ["load_stored_key_package", "purge_key_package_material"];
2936 let (checked, offenders) = record_only_key_package_deletes(include_str!("manager.rs"));
2937
2938 assert!(
2939 checked >= 8,
2940 "found only {checked} key package functions; the scan is broken, not the code"
2941 );
2942 let offenders: Vec<_> = offenders
2943 .into_iter()
2944 .filter(|name| !allowed.contains(&name.as_str()))
2945 .collect();
2946 assert!(
2947 offenders.is_empty(),
2948 "record-only delete of a key package in {offenders:?}. Destroy the init \
2949 key too (purge_key_package_material), or keep the record"
2950 );
2951 }
2952
2953 #[test]
2958 fn the_record_delete_scanner_sees_every_function_header_and_crlf() {
2959 let source = [
2960 "impl MlsManager {",
2961 " fn purge_key_package_material(&self) {",
2962 " let t = StorageKeyType::KeyPackage.as_str();",
2963 " self.storage.delete(t, id)?;",
2964 " }",
2965 " pub(crate) fn hidden_crate(&self) {",
2966 " self.storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
2967 " }",
2968 " fn load_stored_key_package(&self) {}",
2969 " pub async fn hidden_async(&self) {",
2970 " self.storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
2971 " }",
2972 "}",
2973 "fn hidden_free() {",
2974 " storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
2975 "}",
2976 "#[cfg(test)]",
2977 "mod tests {",
2978 " fn in_tests() {",
2979 " storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
2980 " }",
2981 "}",
2982 ]
2983 .join("\r\n");
2984
2985 let (checked, offenders) = record_only_key_package_deletes(&source);
2986
2987 assert_eq!(checked, 4, "the test module is out of scope");
2988 assert_eq!(
2989 offenders,
2990 [
2991 "purge_key_package_material",
2992 "hidden_crate",
2993 "hidden_async",
2994 "hidden_free"
2995 ]
2996 );
2997 }
2998
2999 fn record_only_key_package_deletes(source: &str) -> (usize, Vec<String>) {
3004 let lines: Vec<&str> = source.lines().collect();
3005 let test_module = lines
3006 .windows(2)
3007 .position(|pair| pair[0].trim() == "#[cfg(test)]" && pair[1].starts_with("mod tests"))
3008 .expect("the source has a test module");
3009
3010 let mut functions: Vec<(String, String)> = Vec::new();
3011 for line in &lines[..test_module] {
3012 if let Some(name) = function_header_name(line) {
3013 functions.push((name.to_string(), String::new()));
3014 } else if let Some((_, body)) = functions.last_mut() {
3015 body.push_str(line);
3016 body.push('\n');
3017 }
3018 }
3019
3020 let mut checked = 0;
3021 let mut offenders = Vec::new();
3022 for (name, body) in functions {
3023 if !body.contains("StorageKeyType::KeyPackage.as_str()") {
3024 continue;
3025 }
3026 checked += 1;
3027 if body.contains(".delete(") {
3028 offenders.push(name);
3029 }
3030 }
3031 (checked, offenders)
3032 }
3033
3034 fn function_header_name(line: &str) -> Option<&str> {
3037 let trimmed = line.trim_start();
3038 if !matches!(line.len() - trimmed.len(), 0 | 4) {
3039 return None;
3040 }
3041 let mut rest = trimmed;
3042 loop {
3043 if let Some(after) = rest.strip_prefix("pub(") {
3044 rest = after.split_once(')')?.1.trim_start();
3045 } else if let Some(after) = ["pub ", "async ", "const ", "unsafe "]
3046 .iter()
3047 .find_map(|qualifier| rest.strip_prefix(qualifier))
3048 {
3049 rest = after.trim_start();
3050 } else {
3051 break;
3052 }
3053 }
3054 rest.strip_prefix("fn ")?.split(['(', '<']).next()
3055 }
3056
3057 #[test]
3061 fn test_push_path_claims_an_unclaimed_package_instead_of_stranding_it() {
3062 let manager = create_test_manager("alice");
3063 let legacy = manager.get_or_create_key_package().unwrap();
3064 assert!(legacy.assigned_peer.is_none());
3065
3066 let taken = manager.take_push_key_package(&addr("bob")).unwrap();
3067 assert_eq!(
3068 taken.bundle.package_id, legacy.package_id,
3069 "the pre-existing package was stranded instead of claimed"
3070 );
3071 assert_eq!(
3072 taken.bundle.assigned_peer.as_deref(),
3073 Some(addr("bob").as_str())
3074 );
3075 assert_eq!(manager.count_valid_key_packages().unwrap(), 1);
3076 }
3077
3078 #[test]
3083 fn test_push_pool_ceiling_shares_a_package_and_reports_it() {
3084 let manager = create_test_manager("alice");
3085
3086 for i in 0..MAX_PUSH_KEY_PACKAGES {
3087 let taken = manager.take_push_key_package(&format!("peer{i}")).unwrap();
3088 assert!(
3089 !taken.pool_exhausted,
3090 "pool reported exhausted at {i}, below the ceiling"
3091 );
3092 }
3093 assert_eq!(
3094 manager.count_valid_key_packages().unwrap(),
3095 MAX_PUSH_KEY_PACKAGES
3096 );
3097
3098 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
3099 assert!(over.pool_exhausted, "ceiling breach was not reported");
3100 assert_eq!(
3101 manager.count_valid_key_packages().unwrap(),
3102 MAX_PUSH_KEY_PACKAGES,
3103 "the pool grew past its ceiling"
3104 );
3105
3106 let established = manager.take_push_key_package("peer0").unwrap();
3109 assert!(!established.pool_exhausted);
3110 assert_eq!(established.bundle.assigned_peer.as_deref(), Some("peer0"));
3111 }
3112
3113 #[test]
3119 fn test_ceiling_claims_an_unclaimed_package_rather_than_sharing() {
3120 let manager = create_test_manager("alice");
3121
3122 for i in 0..(MAX_PUSH_KEY_PACKAGES - 1) {
3123 manager.take_push_key_package(&format!("peer{i}")).unwrap();
3124 }
3125 let unclaimed = manager.get_or_create_key_package().unwrap();
3128 assert_eq!(
3129 manager.count_valid_key_packages().unwrap(),
3130 MAX_PUSH_KEY_PACKAGES,
3131 "test premise: the pool is at the ceiling"
3132 );
3133
3134 let taken = manager.take_push_key_package("late-arrival").unwrap();
3135 assert!(
3136 !taken.pool_exhausted,
3137 "shared a package while a claimable one was in the pool"
3138 );
3139 assert_eq!(
3140 taken.bundle.package_id, unclaimed.package_id,
3141 "the claimable package was passed over"
3142 );
3143 assert_eq!(taken.bundle.assigned_peer.as_deref(), Some("late-arrival"));
3144 assert_eq!(
3145 manager.count_valid_key_packages().unwrap(),
3146 MAX_PUSH_KEY_PACKAGES,
3147 "claiming grew the pool"
3148 );
3149
3150 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
3153 assert!(over.pool_exhausted);
3154 }
3155
3156 #[test]
3157 fn test_import_key_package_binds_credential_identity() {
3158 let alice = create_test_manager("alice");
3159 let mallory = create_test_manager("mallory");
3160 let mallory_kp = mallory.generate_key_package().unwrap();
3161
3162 let err = alice
3165 .import_key_package(&addr("bob"), &mallory_kp.key_package_data)
3166 .unwrap_err();
3167 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3168
3169 alice
3171 .import_key_package(&addr("mallory"), &mallory_kp.key_package_data)
3172 .unwrap();
3173 }
3174
3175 fn substituted_key_package(claimed: &str, keys: &SignatureKeyPair) -> Vec<u8> {
3185 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3186 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
3187 keys.store(provider.storage()).unwrap();
3188
3189 let credential = CredentialWithKey {
3190 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
3191 signature_key: keys.public().into(),
3192 };
3193
3194 KeyPackage::builder()
3195 .build(DEFAULT_CIPHERSUITE, &provider, keys, credential)
3196 .unwrap()
3197 .key_package()
3198 .tls_serialize_detached()
3199 .unwrap()
3200 }
3201
3202 #[test]
3203 fn test_credential_identity_check_alone_does_not_stop_a_substitution() {
3204 let (mallorys_keys, _) = test_identity("mallory");
3209 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3210
3211 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3212 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
3213 let key_package = KeyPackageIn::tls_deserialize_exact(&forged)
3214 .unwrap()
3215 .validate(provider.crypto(), ProtocolVersion::Mls10)
3216 .unwrap();
3217
3218 MlsManager::verify_credential_identity(&key_package, &addr("bob"))
3219 .expect("the identity check cannot tell the two bobs apart");
3220 }
3221
3222 #[test]
3223 fn test_substituted_key_package_is_refused_by_the_derivation() {
3224 let alice = create_test_manager("alice");
3229 let real_bob = create_test_manager("bob");
3230 let (mallorys_keys, _) = test_identity("mallory");
3231
3232 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3233 let err = alice.import_key_package(&addr("bob"), &forged).unwrap_err();
3234 assert!(
3235 matches!(
3236 err,
3237 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
3238 ),
3239 "expected an address mismatch, got {:?}",
3240 err
3241 );
3242
3243 let real_kp = real_bob.generate_key_package().unwrap();
3245 alice
3246 .import_key_package(&addr("bob"), &real_kp.key_package_data)
3247 .expect("the real peer's key package derives to its own address");
3248 }
3249
3250 #[test]
3251 fn test_a_nickname_peer_id_is_refused_rather_than_skipping_the_check() {
3252 let alice = create_test_manager("alice");
3256 let bob_kp = create_test_manager("bob").generate_key_package().unwrap();
3257
3258 let err = alice
3259 .import_key_package("bob", &bob_kp.key_package_data)
3260 .unwrap_err();
3261 assert!(
3262 matches!(
3263 err,
3264 MlsError::CredentialIdentityMismatch { .. } | MlsError::InvalidUserId(_)
3265 ),
3266 "a nickname must be refused, got {:?}",
3267 err
3268 );
3269 }
3270
3271 #[test]
3272 fn test_add_group_member_binds_identity_and_derivation() {
3273 let alice = create_test_manager("alice");
3277 let bob = create_test_manager("bob");
3278 let mallory = create_test_manager("mallory");
3279
3280 let group_id = alice.create_group("Test Group").unwrap().group_id;
3281
3282 let mallory_kp = mallory.generate_key_package().unwrap();
3284 let err = alice
3285 .add_group_member(&group_id, &addr("bob"), &mallory_kp.key_package_data)
3286 .unwrap_err();
3287 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3288
3289 let (mallorys_keys, _) = test_identity("mallory");
3291 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3292 let err = alice
3293 .add_group_member(&group_id, &addr("bob"), &forged)
3294 .unwrap_err();
3295 assert!(matches!(err, MlsError::KeyPackageAddressMismatch { .. }));
3296
3297 let bob_kp = bob.generate_key_package().unwrap();
3299 alice
3300 .add_group_member(&group_id, &addr("bob"), &bob_kp.key_package_data)
3301 .expect("the real invitee is admitted");
3302 }
3303
3304 #[test]
3305 fn test_import_key_package_rejects_hostile_user_id() {
3306 let alice = create_test_manager("alice");
3307 let bob = create_test_manager("bob");
3308 let bob_kp = bob.generate_key_package().unwrap();
3309
3310 for hostile in ["", "..", "bob/evil", "bob\\evil", "bob\0evil"] {
3311 let err = alice
3312 .import_key_package(hostile, &bob_kp.key_package_data)
3313 .unwrap_err();
3314 assert!(
3315 matches!(err, MlsError::InvalidUserId(_)),
3316 "expected InvalidUserId for {:?}, got {:?}",
3317 hostile,
3318 err
3319 );
3320 }
3321 }
3322
3323 #[test]
3324 fn test_get_contact_key_package_rejects_poisoned_store() {
3325 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3329 let (alice_keys, alice_address) = test_identity("alice");
3330 seed_identity(&storage, &alice_keys);
3331 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
3332 let mallory = create_test_manager("mallory");
3333 let mallory_kp = mallory.generate_key_package().unwrap();
3334
3335 storage
3336 .store(
3337 StorageKeyType::ContactKeyPackage.as_str(),
3338 &addr("bob"),
3339 &mallory_kp.key_package_data,
3340 )
3341 .unwrap();
3342
3343 let err = alice.create_session(&addr("bob")).unwrap_err();
3345 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3346 }
3347
3348 #[test]
3367 fn test_get_contact_key_package_rejects_a_substituted_package() {
3368 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3369 let (alice_keys, alice_address) = test_identity("alice");
3370 seed_identity(&storage, &alice_keys);
3371 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
3372
3373 let (mallorys_keys, _) = test_identity("mallory");
3375 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3376
3377 storage
3378 .store(
3379 StorageKeyType::ContactKeyPackage.as_str(),
3380 &addr("bob"),
3381 &forged,
3382 )
3383 .unwrap();
3384
3385 let err = alice.create_session(&addr("bob")).unwrap_err();
3386 assert!(
3387 matches!(
3388 err,
3389 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
3390 ),
3391 "a substituted cached package must be refused at use time, got {:?}",
3392 err
3393 );
3394
3395 assert!(!alice.has_session(&addr("bob")).unwrap());
3397 }
3398
3399 #[test]
3400 fn test_replace_session_with_welcome_rejects_hostile_inviter_id() {
3401 let alice = create_test_manager("alice");
3405 let welcome = WelcomeMessage {
3406 group_id: slot("alice", "bob"),
3407 welcome_data: vec![],
3408 inviter_id: "../../etc".to_string(),
3409 group_name: None,
3410 timestamp_ms: 0,
3411 };
3412 let err = alice.replace_session_with_welcome(&welcome).unwrap_err();
3413 assert!(matches!(err, MlsError::InvalidUserId(_)));
3414 }
3415
3416 fn create_test_session() -> (MlsManager, MlsManager) {
3418 let alice = create_test_manager("alice");
3419 let bob = create_test_manager("bob");
3420 let bob_kp = bob.generate_key_package().unwrap();
3421 alice
3422 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3423 .unwrap();
3424 let welcome = alice.create_session(&addr("bob")).unwrap();
3425 bob.join_session(&welcome).unwrap();
3426 (alice, bob)
3427 }
3428
3429 #[test]
3430 fn test_session_decrypt_rejects_spoofed_sender() {
3431 let (alice, bob) = create_test_session();
3432
3433 let ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
3439 let err = bob.decrypt_from_user(&ct, &addr("mallory")).unwrap_err();
3440 assert!(
3441 matches!(err, MlsError::SessionIdentityMismatch { .. }),
3442 "spoofed sender must be rejected by the slot binding, got {:?}",
3443 err
3444 );
3445
3446 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
3451 assert_eq!(pt.as_deref(), Some(&b"hello"[..]));
3452
3453 }
3457
3458 #[test]
3459 fn test_epoch_fork_classifies_as_session_desync() {
3460 let (alice, bob) = create_test_session();
3465
3466 let group_id = slot("alice", "bob");
3469 alice.update_keys(&group_id).unwrap();
3470
3471 let ct = alice.encrypt_for_user(&addr("bob"), b"after-fork").unwrap();
3474 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
3475 assert!(
3476 matches!(err, MlsError::SessionDesync(_)),
3477 "epoch fork must classify as SessionDesync, got {:?}",
3478 err
3479 );
3480 }
3481
3482 fn push_varint(out: &mut Vec<u8>, len: usize) {
3495 match len {
3496 0..=63 => out.push(len as u8),
3497 64..=16_383 => {
3498 out.push(0x40 | (len >> 8) as u8);
3499 out.push((len & 0xff) as u8);
3500 }
3501 _ => panic!("test payloads stay under the 2-byte varint ceiling"),
3502 }
3503 }
3504
3505 fn forge_private_message(group_id: &str, epoch: u64) -> Vec<u8> {
3506 let mut out = Vec::new();
3507 out.extend_from_slice(&1u16.to_be_bytes()); out.extend_from_slice(&2u16.to_be_bytes()); push_varint(&mut out, group_id.len());
3510 out.extend_from_slice(group_id.as_bytes());
3511 out.extend_from_slice(&epoch.to_be_bytes());
3512 out.push(1); out.push(0); out.push(8); out.extend_from_slice(&[0u8; 8]);
3516 out.push(16); out.extend_from_slice(&[0u8; 16]);
3518 out
3519 }
3520
3521 #[test]
3522 fn test_forged_frame_reaches_session_desync_without_any_key_material() {
3523 let (_alice, bob) = create_test_session();
3536
3537 let forged = EncryptedMessage {
3538 group_id: slot("alice", "bob"),
3539 message_type: MlsMessageType::Application,
3540 epoch: 9_999,
3541 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
3542 sender_id: addr("alice"),
3543 timestamp_ms: 0,
3544 };
3545
3546 let err = bob.decrypt_from_user(&forged, &addr("alice")).unwrap_err();
3547 assert!(
3548 matches!(err, MlsError::SessionDesync(_)),
3549 "a forged future-epoch frame reaches the desync classification pre-AEAD, got {:?}",
3550 err
3551 );
3552 }
3553
3554 #[test]
3555 fn test_forged_frame_naming_a_foreign_slot_is_rejected_before_desync() {
3556 let (_alice, bob) = create_test_session();
3562
3563 let forged = EncryptedMessage {
3564 group_id: slot("alice", "bob"),
3565 message_type: MlsMessageType::Application,
3566 epoch: 9_999,
3567 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
3568 sender_id: addr("mallory"),
3569 timestamp_ms: 0,
3570 };
3571
3572 let err = bob
3573 .decrypt_from_user(&forged, &addr("mallory"))
3574 .unwrap_err();
3575 assert!(
3576 matches!(err, MlsError::SessionIdentityMismatch { .. }),
3577 "a frame naming another pair's slot must not classify as recoverable, got {:?}",
3578 err
3579 );
3580 }
3581
3582 #[test]
3583 fn test_corrupt_ciphertext_is_not_classified_as_session_desync() {
3584 let (alice, bob) = create_test_session();
3590 let mut ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
3591 for b in ct.ciphertext.iter_mut() {
3593 *b ^= 0xFF;
3594 }
3595 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
3596 assert!(
3597 !matches!(err, MlsError::SessionDesync(_)),
3598 "corrupt ciphertext must not be treated as a recoverable desync, got {:?}",
3599 err
3600 );
3601 }
3602
3603 #[test]
3604 fn test_join_session_binds_slot_to_inviter() {
3605 let bob = create_test_manager("bob");
3612
3613 let hijack = WelcomeMessage {
3616 group_id: slot("alice", "bob"),
3617 welcome_data: vec![], inviter_id: addr("mallory"),
3619 group_name: None,
3620 timestamp_ms: 0,
3621 };
3622
3623 let err = bob.join_session(&hijack).unwrap_err();
3625 assert!(
3626 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3627 "join_session: expected WelcomeIdentityMismatch, got {:?}",
3628 err
3629 );
3630 let err = bob.replace_session_with_welcome(&hijack).unwrap_err();
3631 assert!(
3632 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3633 "replace_session_with_welcome: expected WelcomeIdentityMismatch, got {:?}",
3634 err
3635 );
3636
3637 assert!(!bob.has_session(&addr("alice")).unwrap());
3639 assert!(!bob.has_session(&addr("mallory")).unwrap());
3640
3641 let bob_kp = bob.generate_key_package().unwrap();
3645 let mallory = create_test_manager("mallory");
3646 mallory
3647 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3648 .unwrap();
3649 let legit = mallory.create_session(&addr("bob")).unwrap();
3650 bob.join_session(&legit).unwrap();
3651 assert!(bob.has_session(&addr("mallory")).unwrap());
3652 }
3653
3654 #[test]
3655 fn test_join_group_rejects_session_namespace() {
3656 let bob = create_test_manager("bob");
3664
3665 let squat = WelcomeMessage {
3666 group_id: slot("alice", "bob"),
3667 welcome_data: vec![], inviter_id: addr("mallory"),
3669 group_name: None,
3670 timestamp_ms: 0,
3671 };
3672
3673 let err = bob.join_group(&squat).unwrap_err();
3674 assert!(
3675 matches!(err, MlsError::ReservedSessionNamespace { .. }),
3676 "join_group: expected ReservedSessionNamespace, got {:?}",
3677 err
3678 );
3679
3680 assert!(!bob.has_session(&addr("alice")).unwrap());
3682
3683 let legit_ns = WelcomeMessage {
3687 group_id: GroupId::new("group:0bd6e5f2-3a70-4a4e-9c3f-1c1f2a3b4c5d").unwrap(),
3688 welcome_data: vec![],
3689 inviter_id: addr("mallory"),
3690 group_name: None,
3691 timestamp_ms: 0,
3692 };
3693 let err = bob.join_group(&legit_ns).unwrap_err();
3694 assert!(
3695 !matches!(err, MlsError::ReservedSessionNamespace { .. }),
3696 "a group:-namespaced Welcome must not trip the session-namespace guard, got {:?}",
3697 err
3698 );
3699 }
3700
3701 #[test]
3702 fn test_join_group_rejects_embedded_group_id_mismatch() {
3703 let bob = create_test_manager("bob");
3712 let mallory = create_test_manager("mallory");
3713
3714 let bob_kp = bob.generate_key_package().unwrap();
3716 let embedded = GroupId::new("group:11111111-1111-4111-8111-111111111111").unwrap();
3717 let cred = mallory.get_credential().unwrap();
3718 let signer = mallory.get_signer().unwrap();
3719 mallory
3720 .group_manager
3721 .create_group(&embedded, &cred, &signer)
3722 .unwrap();
3723 let (welcome, _commit) = mallory
3724 .add_group_member(&embedded, &addr("bob"), &bob_kp.key_package_data)
3725 .unwrap();
3726 assert_eq!(welcome.group_id, embedded);
3727
3728 let wire = GroupId::new("group:22222222-2222-4222-8222-222222222222").unwrap();
3732 let tampered = WelcomeMessage {
3733 group_id: wire.clone(),
3734 ..welcome
3735 };
3736
3737 let err = bob.join_group(&tampered).unwrap_err();
3738 assert!(
3739 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3740 "expected WelcomeGroupIdMismatch, got {:?}",
3741 err
3742 );
3743
3744 assert!(bob.group_manager.load_group(&wire).unwrap().is_none());
3746 assert!(bob.group_manager.load_group(&embedded).unwrap().is_none());
3747 }
3748
3749 #[test]
3750 fn test_welcome_embedded_id_cannot_hijack_session_slot() {
3751 let alice = create_test_manager("alice");
3760 let bob = create_test_manager("bob");
3761 let bob_kp = bob.generate_key_package().unwrap();
3762 alice
3763 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3764 .unwrap();
3765 let welcome = alice.create_session(&addr("bob")).unwrap();
3766 bob.join_session(&welcome).unwrap();
3767 assert!(bob.has_session(&addr("alice")).unwrap());
3768
3769 let mallory = create_test_manager("mallory");
3771 let bob_kp2 = bob.generate_key_package().unwrap();
3772 let squat = slot("alice", "bob");
3773 let cred = mallory.get_credential().unwrap();
3774 let signer = mallory.get_signer().unwrap();
3775 mallory
3776 .group_manager
3777 .create_group(&squat, &cred, &signer)
3778 .unwrap();
3779 let (mal_welcome, _commit) = mallory
3780 .add_group_member(&squat, &addr("bob"), &bob_kp2.key_package_data)
3781 .unwrap();
3782
3783 let attack = WelcomeMessage {
3785 group_id: slot("bob", "mallory"),
3786 welcome_data: mal_welcome.welcome_data,
3787 inviter_id: addr("mallory"),
3788 group_name: None,
3789 timestamp_ms: 0,
3790 };
3791
3792 let err = bob.join_session(&attack).unwrap_err();
3793 assert!(
3794 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3795 "expected WelcomeGroupIdMismatch, got {:?}",
3796 err
3797 );
3798
3799 assert!(bob.has_session(&addr("alice")).unwrap());
3801 let ct = bob
3802 .encrypt_for_user(&addr("alice"), b"still private")
3803 .unwrap();
3804 let pt = alice.decrypt_from_user(&ct, &addr("bob")).unwrap();
3805 assert_eq!(pt.as_deref(), Some(&b"still private"[..]));
3806 }
3807
3808 fn create_test_group_with_bob() -> (MlsManager, MlsManager, GroupId) {
3811 let alice = create_test_manager("alice");
3812 let bob = create_test_manager("bob");
3813 let bob_kp = bob.generate_key_package().unwrap();
3814 alice
3815 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3816 .unwrap();
3817 let info = alice.create_group("Test Group").unwrap();
3818 let gid = info.group_id.clone();
3819 let (welcome, _commit) = alice
3820 .add_group_member(&gid, &addr("bob"), &bob_kp.key_package_data)
3821 .unwrap();
3822 bob.join_group(&welcome).unwrap();
3823 (alice, bob, gid)
3824 }
3825
3826 #[test]
3827 fn test_group_decrypt_rejects_spoofed_sender() {
3828 let (alice, bob, gid) = create_test_group_with_bob();
3829
3830 let ct = alice.encrypt_for_group(&gid, b"group message").unwrap();
3831 let err = bob.decrypt_from_group(&ct, &addr("mallory")).unwrap_err();
3832 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3833
3834 let ct2 = alice.encrypt_for_group(&gid, b"another one").unwrap();
3835 let pt = bob.decrypt_from_group(&ct2, &addr("alice")).unwrap();
3836 assert_eq!(pt.as_deref(), Some(&b"another one"[..]));
3837 }
3838
3839 #[test]
3840 fn test_group_commit_with_spoofed_sender_rejected_before_merge() {
3841 let (alice, bob, gid) = create_test_group_with_bob();
3842
3843 let commit = alice.update_keys(&gid).unwrap();
3847 let epoch_before = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3848
3849 let err = bob
3850 .decrypt_from_group(&commit, &addr("mallory"))
3851 .unwrap_err();
3852 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3853
3854 let epoch_after = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3855 assert_eq!(
3856 epoch_before, epoch_after,
3857 "spoofed commit must not advance group state"
3858 );
3859 }
3860
3861 #[test]
3862 fn test_no_session_initially() {
3863 let manager = create_test_manager("alice");
3864 assert!(!manager.has_session(&addr("bob")).unwrap());
3865 assert!(manager.list_sessions().unwrap().is_empty());
3866 }
3867
3868 #[test]
3869 fn test_no_groups_initially() {
3870 let manager = create_test_manager("alice");
3871 assert!(manager.list_groups().unwrap().is_empty());
3872 }
3873
3874 #[test]
3875 fn test_group_creation() {
3876 let manager = create_test_manager("alice");
3877 let info = manager.create_group("Test Group").unwrap();
3878
3879 assert_eq!(info.name, Some("Test Group".to_string()));
3880 assert!(!info.is_session);
3881 assert_eq!(info.members.len(), 1);
3882
3883 let groups = manager.list_groups().unwrap();
3884 assert_eq!(groups.len(), 1);
3885 }
3886
3887 #[test]
3898 fn test_both_create_adopt_is_non_destructive_on_welcome_retransmit() {
3899 let alice = create_test_manager("alice");
3901 let bob = create_test_manager("bob");
3902
3903 let alice_kp = alice.generate_key_package().unwrap();
3905 let bob_kp = bob.generate_key_package().unwrap();
3906 alice
3907 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3908 .unwrap();
3909 bob.import_key_package(&addr("alice"), &alice_kp.key_package_data)
3910 .unwrap();
3911
3912 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());
3916
3917 bob.join_session(&alice_welcome).unwrap();
3920 assert!(bob.has_session(&addr("alice")).unwrap());
3921
3922 let ct = alice
3924 .encrypt_for_user(&addr("bob"), b"hello over the converged group")
3925 .unwrap();
3926 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
3927 assert_eq!(pt.as_deref(), Some(&b"hello over the converged group"[..]));
3928
3929 let retransmit = bob.join_session(&alice_welcome);
3932 assert!(
3933 retransmit.is_err(),
3934 "re-staging a consumed key package should fail"
3935 );
3936
3937 assert!(
3940 bob.has_session(&addr("alice")).unwrap(),
3941 "duplicate Welcome must not brick the converged session"
3942 );
3943
3944 let ct2 = alice
3946 .encrypt_for_user(&addr("bob"), b"still converged after retransmit")
3947 .unwrap();
3948 let pt2 = bob.decrypt_from_user(&ct2, &addr("alice")).unwrap();
3949 assert_eq!(
3950 pt2.as_deref(),
3951 Some(&b"still converged after retransmit"[..])
3952 );
3953 }
3954
3955 #[test]
3961 fn test_out_of_order_decryption_within_sender_ratchet_tolerance() {
3962 let alice = create_test_manager("alice");
3963 let bob = create_test_manager("bob");
3964
3965 let bob_kp = bob.generate_key_package().unwrap();
3966 alice
3967 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3968 .unwrap();
3969 let welcome = alice.create_session(&addr("bob")).unwrap();
3970 bob.join_session(&welcome).unwrap();
3971
3972 let ciphertexts: Vec<_> = (0..40)
3973 .map(|i| {
3974 alice
3975 .encrypt_for_user(&addr("bob"), format!("chunk {}", i).as_bytes())
3976 .unwrap()
3977 })
3978 .collect();
3979
3980 let pt = bob
3983 .decrypt_from_user(&ciphertexts[39], &addr("alice"))
3984 .unwrap();
3985 assert_eq!(pt.as_deref(), Some(&b"chunk 39"[..]));
3986
3987 let pt = bob
3990 .decrypt_from_user(&ciphertexts[10], &addr("alice"))
3991 .unwrap();
3992 assert_eq!(pt.as_deref(), Some(&b"chunk 10"[..]));
3993
3994 let res = bob.decrypt_from_user(&ciphertexts[0], &addr("alice"));
3997 assert!(
3998 !matches!(res, Ok(Some(_))),
3999 "generation beyond the tolerance must not decrypt"
4000 );
4001 }
4002
4003 const RFC8032_TV1_PK: [u8; 32] = [
4005 0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07,
4006 0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07,
4007 0x51, 0x1a,
4008 ];
4009
4010 const RFC8032_TV1_ADDRESS: &str = "off1qysluvwl5922yctzd0u9gpr06gn3k7ldfvgtwgvn";
4015
4016 #[test]
4024 fn derive_address_matches_the_pinned_vector() {
4025 let address = MlsManager::derive_address(&RFC8032_TV1_PK).expect("32-byte key derives");
4026 assert_eq!(address.to_string(), RFC8032_TV1_ADDRESS);
4027 assert_eq!(
4030 RFC8032_TV1_ADDRESS.parse::<Address>().expect("parses"),
4031 address
4032 );
4033 }
4034
4035 #[test]
4036 fn derive_address_rejects_wrong_key_lengths() {
4037 for len in [0usize, 31, 33, 64] {
4038 let key = vec![0u8; len];
4039 assert!(
4040 matches!(
4041 MlsManager::derive_address(&key),
4042 Err(MlsError::InvalidPublicKey(_))
4043 ),
4044 "{len}-byte key must be rejected"
4045 );
4046 }
4047 }
4048
4049 #[test]
4050 fn deprecated_derive_agrees_with_derive_address() {
4051 #[allow(deprecated)]
4054 let legacy = MlsManager::derive_user_id_from_public_key(&RFC8032_TV1_PK);
4055 assert_eq!(legacy, RFC8032_TV1_ADDRESS);
4056 assert_eq!(
4057 legacy,
4058 MlsManager::derive_address(&RFC8032_TV1_PK)
4059 .expect("derives")
4060 .to_string()
4061 );
4062 }
4063
4064 #[test]
4065 fn load_or_create_identity_mints_once_and_is_idempotent() {
4066 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4067
4068 let (first_keys, first_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4069 let (second_keys, second_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4070
4071 assert_eq!(first_keys.public(), second_keys.public());
4073 assert_eq!(first_address, second_address);
4074 assert_eq!(
4075 MlsManager::derive_address(first_keys.public()).unwrap(),
4076 first_address,
4077 "the returned address must be the stored key's derivation"
4078 );
4079 }
4080
4081 #[test]
4082 fn load_or_create_identity_adopts_an_existing_key() {
4083 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4084 let (keys, address) = test_identity("alice");
4085 seed_identity(&storage, &keys);
4086
4087 let (loaded, loaded_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4088
4089 assert_eq!(loaded.public(), keys.public());
4090 assert_eq!(loaded_address, address);
4091 }
4092
4093 #[test]
4094 fn manager_bootstrapped_at_its_derived_address_loads_that_identity() {
4095 let (manager, address) = create_addressed_manager("alice");
4096
4097 assert_eq!(manager.user_id(), address.to_string());
4098 assert_eq!(
4099 MlsManager::derive_address(&manager.get_identity_public_key().unwrap()).unwrap(),
4100 address,
4101 "the manager's identity key must derive to the address it claims"
4102 );
4103 }
4104
4105 #[test]
4110 fn manager_refuses_an_address_whose_key_is_not_the_stored_one() {
4111 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4112 let (alice_keys, alice_address) = test_identity("alice");
4113 let (_, bob_address) = test_identity("bob");
4114 seed_identity(&storage, &alice_keys);
4115
4116 let Err(err) = MlsManager::new(bob_address.to_string(), storage) else {
4117 panic!("a manager must not claim an address it holds no key for");
4118 };
4119
4120 match err {
4121 MlsError::IdentityAddressMismatch { expected, derived } => {
4122 assert_eq!(expected, bob_address.to_string());
4123 assert_eq!(derived, alice_address.to_string());
4124 }
4125 other => panic!("expected IdentityAddressMismatch, got {other:?}"),
4126 }
4127 }
4128
4129 #[test]
4133 fn manager_refuses_to_claim_an_address_with_no_stored_identity() {
4134 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4135 let (_, address) = test_identity("alice");
4136
4137 let Err(err) = MlsManager::new(address.to_string(), storage.clone()) else {
4138 panic!("a manager must not mint a key and claim a different address");
4139 };
4140 assert!(matches!(err, MlsError::IdentityAddressMismatch { .. }));
4141
4142 assert!(
4143 storage
4144 .load(StorageKeyType::Identity.as_str(), "key_pair")
4145 .unwrap()
4146 .is_none(),
4147 "a refused bootstrap must not leave a key behind"
4148 );
4149 }
4150
4151 #[test]
4163 fn manager_with_a_non_address_id_skips_the_binding_check() {
4164 let manager = MlsManager::new("alice", Arc::new(InMemoryStorage::new())).unwrap();
4165 assert_eq!(manager.user_id(), "alice");
4166 }
4167
4168 struct Impostor {
4181 provider: MlsProvider,
4182 keys: SignatureKeyPair,
4183 key_package: Vec<u8>,
4184 group: Option<MlsGroup>,
4185 }
4186
4187 impl Impostor {
4188 fn claiming(claimed: &str, key_label: &str) -> Self {
4191 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4192 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
4193 let (keys, _) = test_identity(key_label);
4194 keys.store(provider.storage()).unwrap();
4195
4196 let credential = CredentialWithKey {
4197 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
4198 signature_key: keys.public().into(),
4199 };
4200 let key_package = KeyPackage::builder()
4201 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
4202 .unwrap()
4203 .key_package()
4204 .tls_serialize_detached()
4205 .unwrap();
4206
4207 Self {
4208 provider,
4209 keys,
4210 key_package,
4211 group: None,
4212 }
4213 }
4214
4215 fn join(&mut self, welcome: &MlsMessageOut) {
4217 let bytes = welcome.tls_serialize_detached().unwrap();
4218 let welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
4219 .unwrap()
4220 .extract()
4221 {
4222 MlsMessageBodyIn::Welcome(w) => w,
4223 _ => panic!("not a welcome"),
4224 };
4225 let cfg = MlsGroupJoinConfig::builder()
4226 .use_ratchet_tree_extension(true)
4227 .build();
4228 self.group = Some(
4229 StagedWelcome::new_from_welcome(&self.provider, &cfg, welcome, None)
4230 .unwrap()
4231 .into_group(&self.provider)
4232 .unwrap(),
4233 );
4234 }
4235
4236 fn merge_commit(&mut self, commit_bytes: &[u8]) {
4242 let group = self.group.as_mut().expect("join first");
4243 let message = MlsMessageIn::tls_deserialize_exact(commit_bytes)
4244 .unwrap()
4245 .try_into_protocol_message()
4246 .unwrap();
4247 let processed = group.process_message(&self.provider, message).unwrap();
4248 match processed.into_content() {
4249 ProcessedMessageContent::StagedCommitMessage(staged) => {
4250 group.merge_staged_commit(&self.provider, *staged).unwrap();
4251 }
4252 _ => panic!("not a commit"),
4253 }
4254 }
4255
4256 fn encrypt(&mut self, plaintext: &[u8]) -> Vec<u8> {
4258 self.group
4259 .as_mut()
4260 .expect("join first")
4261 .create_message(&self.provider, &self.keys, plaintext)
4262 .unwrap()
4263 .tls_serialize_detached()
4264 .unwrap()
4265 }
4266 }
4267
4268 fn add_member_bypassing_checks(
4276 manager: &MlsManager,
4277 group_id: &GroupId,
4278 key_package_bytes: &[u8],
4279 ) -> (MlsMessageOut, MlsMessageOut) {
4280 let mut group = manager
4281 .group_manager
4282 .load_group(group_id)
4283 .unwrap()
4284 .expect("group exists");
4285 let key_package = KeyPackageIn::tls_deserialize_exact(key_package_bytes)
4286 .unwrap()
4287 .validate(
4288 manager.group_manager.provider().crypto(),
4289 ProtocolVersion::Mls10,
4290 )
4291 .unwrap();
4292 let signer = manager.get_signer().unwrap();
4293 let (commit, welcome) = manager
4294 .group_manager
4295 .add_member(&mut group, key_package, &signer)
4296 .unwrap();
4297 manager.group_manager.save_group(group_id, &group).unwrap();
4298 (commit, welcome)
4299 }
4300
4301 fn commit_message(
4302 group_id: &GroupId,
4303 commit: &MlsMessageOut,
4304 sender: &str,
4305 ) -> EncryptedMessage {
4306 EncryptedMessage {
4307 group_id: group_id.clone(),
4308 message_type: MlsMessageType::Commit,
4309 epoch: 0,
4310 ciphertext: commit.tls_serialize_detached().unwrap(),
4311 sender_id: sender.to_string(),
4312 timestamp_ms: 0,
4313 }
4314 }
4315
4316 fn app_message(group_id: &GroupId, ciphertext: Vec<u8>, sender: &str) -> EncryptedMessage {
4317 EncryptedMessage {
4318 group_id: group_id.clone(),
4319 message_type: MlsMessageType::Application,
4320 epoch: 0,
4321 ciphertext,
4322 sender_id: sender.to_string(),
4323 timestamp_ms: 0,
4324 }
4325 }
4326
4327 #[test]
4335 fn test_committed_forged_leaf_is_refused_and_cannot_speak_as_its_victim() {
4336 let mallory = create_test_manager("mallory");
4337 let alice = create_test_manager("alice");
4338 let alice_kp = alice.generate_key_package().unwrap();
4339
4340 let gid = mallory.create_group("Team").unwrap().group_id;
4341 let (welcome, _) = mallory
4342 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4343 .unwrap();
4344 alice.join_group(&welcome).unwrap();
4345 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
4346
4347 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4349 assert!(matches!(
4350 mallory
4351 .add_group_member(&gid, &addr("bob"), &impostor.key_package)
4352 .unwrap_err(),
4353 MlsError::KeyPackageAddressMismatch { .. }
4354 ));
4355 let (commit, forged_welcome) =
4356 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4357
4358 let err = alice
4360 .decrypt_from_group(
4361 &commit_message(&gid, &commit, &addr("mallory")),
4362 &addr("mallory"),
4363 )
4364 .unwrap_err();
4365 assert!(
4366 matches!(
4367 err,
4368 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4369 if *claimed == addr("bob") && *context == crate::error::LeafSource::CommitAdd
4370 ),
4371 "expected a CommitAdd binding refusal, got {err:?}"
4372 );
4373
4374 let after = alice.get_group_info(&gid).unwrap().unwrap();
4376 assert_eq!(
4377 after.epoch, epoch_before,
4378 "a refused commit advanced the epoch"
4379 );
4380 assert!(
4381 !after.members.contains(&addr("bob")),
4382 "roster: {:?}",
4383 after.members
4384 );
4385
4386 impostor.join(&forged_welcome);
4394 let forged = impostor.encrypt(b"send me the recovery phrase");
4395 assert!(
4396 alice
4397 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
4398 .is_err(),
4399 "a forged leaf's message was delivered"
4400 );
4401 }
4402
4403 #[test]
4407 fn test_welcome_carrying_a_forged_leaf_is_refused() {
4408 let mallory = create_test_manager("mallory");
4409 let alice = create_test_manager("alice");
4410
4411 let gid = mallory.create_group("Team").unwrap().group_id;
4412 let impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4413 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4414
4415 let alice_kp = alice.generate_key_package().unwrap();
4417 let (welcome, _) = mallory
4418 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4419 .unwrap();
4420
4421 let err = alice.join_group(&welcome).unwrap_err();
4422 assert!(
4423 matches!(
4424 err,
4425 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4426 if *claimed == addr("bob") && *context == crate::error::LeafSource::WelcomeTree
4427 ),
4428 "expected a WelcomeTree binding refusal, got {err:?}"
4429 );
4430
4431 assert!(alice.get_group_info(&gid).unwrap().is_none());
4433 }
4434
4435 #[test]
4440 fn test_session_welcome_carrying_a_forged_leaf_is_refused_non_destructively() {
4441 let alice = create_test_manager("alice");
4442 let bob = create_test_manager("bob");
4443 let mallory = create_test_manager("mallory");
4444
4445 let bob_kp = bob.generate_key_package().unwrap();
4447 alice
4448 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4449 .unwrap();
4450 bob.join_session(&alice.create_session(&addr("bob")).unwrap())
4451 .unwrap();
4452 assert!(bob.has_session(&addr("alice")).unwrap());
4453
4454 let bob_kp2 = bob.generate_key_package().unwrap();
4457 let slot_id = slot("bob", "mallory");
4458 let cred = mallory.get_credential().unwrap();
4459 let signer = mallory.get_signer().unwrap();
4460 mallory
4461 .group_manager
4462 .create_group(&slot_id, &cred, &signer)
4463 .unwrap();
4464 let impostor = Impostor::claiming(&addr("carol"), "mallorys-second-key");
4465 add_member_bypassing_checks(&mallory, &slot_id, &impostor.key_package);
4466 let (welcome, _) = mallory
4467 .add_group_member(&slot_id, &addr("bob"), &bob_kp2.key_package_data)
4468 .unwrap();
4469
4470 let err = bob.join_session(&welcome).unwrap_err();
4471 assert!(
4472 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
4473 if *context == crate::error::LeafSource::WelcomeTree),
4474 "expected a WelcomeTree binding refusal, got {err:?}"
4475 );
4476
4477 assert!(bob.has_session(&addr("alice")).unwrap());
4479 let ct = bob
4480 .encrypt_for_user(&addr("alice"), b"still private")
4481 .unwrap();
4482 assert_eq!(
4483 alice
4484 .decrypt_from_user(&ct, &addr("bob"))
4485 .unwrap()
4486 .as_deref(),
4487 Some(&b"still private"[..])
4488 );
4489 }
4490
4491 #[test]
4499 fn test_forged_leaf_seated_behind_the_gates_still_cannot_speak() {
4500 let mallory = create_test_manager("mallory");
4501 let alice = create_test_manager("alice");
4502
4503 let gid = mallory.create_group("Team").unwrap().group_id;
4504 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4505 let (_, forged_welcome) =
4506 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4507 impostor.join(&forged_welcome);
4508
4509 let alice_kp = alice.generate_key_package().unwrap();
4510 let (welcome, add_alice_commit) = mallory
4511 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4512 .unwrap();
4513 impostor.merge_commit(&add_alice_commit.ciphertext);
4516
4517 let bytes = welcome.welcome_data.clone();
4520 let staged_welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
4521 .unwrap()
4522 .extract()
4523 {
4524 MlsMessageBodyIn::Welcome(w) => w,
4525 _ => panic!("not a welcome"),
4526 };
4527 let cfg = MlsGroupJoinConfig::builder()
4528 .use_ratchet_tree_extension(true)
4529 .build();
4530 let group = StagedWelcome::new_from_welcome(&alice.provider, &cfg, staged_welcome, None)
4531 .unwrap()
4532 .into_group(&alice.provider)
4533 .unwrap();
4534 alice.group_manager.save_group(&gid, &group).unwrap();
4535
4536 assert!(
4538 group
4539 .members()
4540 .any(|m| m.credential.serialized_content() == addr("bob").as_bytes()),
4541 "test premise: the forged leaf must be installed"
4542 );
4543
4544 let forged = impostor.encrypt(b"send me the recovery phrase");
4546 let err = alice
4547 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
4548 .unwrap_err();
4549 assert!(
4550 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
4551 if *context == crate::error::LeafSource::MessageSender),
4552 "expected a MessageSender binding refusal, got {err:?}"
4553 );
4554
4555 let info = alice.get_group_info(&gid).unwrap().unwrap();
4558 assert!(
4559 !info.members.contains(&addr("bob")),
4560 "roster surfaced an unproven member: {:?}",
4561 info.members
4562 );
4563 assert!(
4564 info.members.contains(&addr("mallory")),
4565 "{:?}",
4566 info.members
4567 );
4568 }
4569
4570 #[test]
4584 fn test_member_renaming_their_own_leaf_to_a_peers_address_is_refused() {
4585 let (alice, bob, gid) = create_test_group_with_bob();
4589 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
4590
4591 let mut group = bob.group_manager.load_group(&gid).unwrap().unwrap();
4592 let signer = bob.get_signer().unwrap();
4593 let forged = CredentialWithKey {
4594 credential: Credential::new(CredentialType::Basic, addr("alice").as_bytes().to_vec()),
4597 signature_key: signer.public().into(),
4598 };
4599 let params = LeafNodeParameters::builder()
4600 .with_credential_with_key(forged)
4601 .build();
4602 let bundle = group
4603 .self_update(&bob.provider, &signer, params)
4604 .expect("MLS permits a credential change; the AS is what refuses it");
4605 group.merge_pending_commit(&bob.provider).unwrap();
4606 bob.group_manager.save_group(&gid, &group).unwrap();
4607 let (commit, _, _) = bundle.into_contents();
4608
4609 let err = alice
4610 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4611 .unwrap_err();
4612 assert!(
4613 matches!(
4614 err,
4615 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4616 if *claimed == addr("alice") && *context == crate::error::LeafSource::CommitPath
4617 ),
4618 "expected a CommitPath binding refusal, got {err:?}"
4619 );
4620
4621 let after = alice.get_group_info(&gid).unwrap().unwrap();
4624 assert_eq!(after.epoch, epoch_before);
4625 assert!(after.members.contains(&addr("bob")), "{:?}", after.members);
4626 assert_eq!(
4627 after
4628 .members
4629 .iter()
4630 .filter(|m| **m == addr("alice"))
4631 .count(),
4632 1,
4633 "alice must appear once, as herself: {:?}",
4634 after.members
4635 );
4636 }
4637
4638 #[test]
4641 fn test_leaf_with_a_nickname_credential_is_refused() {
4642 let mallory = create_test_manager("mallory");
4643 let alice = create_test_manager("alice");
4644 let alice_kp = alice.generate_key_package().unwrap();
4645
4646 let gid = mallory.create_group("Team").unwrap().group_id;
4647 let impostor = Impostor::claiming("bob", "mallorys-second-key");
4648 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4649 let (welcome, _) = mallory
4650 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4651 .unwrap();
4652
4653 let err = alice.join_group(&welcome).unwrap_err();
4654 assert!(
4655 matches!(err, MlsError::LeafAddressMismatch { ref claimed, .. } if claimed == "bob"),
4656 "a nickname credential was accepted or misreported: {err:?}"
4657 );
4658 }
4659
4660 #[test]
4668 fn test_key_rotation_and_ordinary_traffic_still_pass_the_binding() {
4669 let (alice, bob, gid) = create_test_group_with_bob();
4670
4671 let commit = alice.update_keys(&gid).unwrap();
4672 bob.decrypt_from_group(&commit, &addr("alice"))
4673 .expect("an honest key rotation must still merge");
4674
4675 let ct = alice.encrypt_for_group(&gid, b"after rotation").unwrap();
4676 assert_eq!(
4677 bob.decrypt_from_group(&ct, &addr("alice"))
4678 .unwrap()
4679 .as_deref(),
4680 Some(&b"after rotation"[..])
4681 );
4682
4683 assert!(bob
4685 .get_group_info(&gid)
4686 .unwrap()
4687 .unwrap()
4688 .members
4689 .contains(&addr("bob")));
4690 }
4691
4692 #[test]
4700 fn test_welcome_whose_group_context_has_external_senders_is_refused() {
4701 let mallory = create_test_manager("mallory");
4702 let alice = create_test_manager("alice");
4703
4704 let gid = mallory.create_group("Team").unwrap().group_id;
4705
4706 {
4708 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
4709 let signer = mallory.get_signer().unwrap();
4710 let outsider =
4711 SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
4712 let external = ExternalSender::new(
4713 outsider.public().into(),
4714 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
4715 );
4716 group
4717 .commit_builder()
4718 .propose_group_context_extensions(Extensions::single(Extension::ExternalSenders(
4719 vec![external],
4720 )))
4721 .load_psks(mallory.provider.storage())
4722 .unwrap()
4723 .build(
4724 mallory.provider.rand(),
4725 mallory.provider.crypto(),
4726 &signer,
4727 |_| true,
4728 )
4729 .unwrap()
4730 .stage_commit(&mallory.provider)
4731 .unwrap();
4732 group.merge_pending_commit(&mallory.provider).unwrap();
4733 mallory.group_manager.save_group(&gid, &group).unwrap();
4734 }
4735
4736 let alice_kp = alice.generate_key_package().unwrap();
4737 let (welcome, _) = mallory
4738 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4739 .unwrap();
4740
4741 let err = alice.join_group(&welcome).unwrap_err();
4742 assert!(
4743 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4744 if detail.contains("ExternalSenders")),
4745 "expected the invite to be declined for its ExternalSenders context, got {err:?}"
4746 );
4747 assert!(
4748 alice.get_group_info(&gid).unwrap().is_none(),
4749 "a declined Welcome must not install the group"
4750 );
4751 }
4752
4753 #[test]
4762 fn test_commit_proposing_external_senders_is_refused() {
4763 let (alice, mallory, gid) = create_test_group_with_bob();
4764
4765 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
4766 let signer = mallory.get_signer().unwrap();
4767
4768 let outsider = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
4770 let external = ExternalSender::new(
4771 outsider.public().into(),
4772 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
4773 );
4774 let extensions = Extensions::single(Extension::ExternalSenders(vec![external]));
4775
4776 let bundle = group
4777 .commit_builder()
4778 .propose_group_context_extensions(extensions)
4779 .load_psks(mallory.provider.storage())
4780 .unwrap()
4781 .build(
4782 mallory.provider.rand(),
4783 mallory.provider.crypto(),
4784 &signer,
4785 |_| true,
4786 )
4787 .unwrap()
4788 .stage_commit(&mallory.provider)
4789 .unwrap();
4790 let (commit, _, _) = bundle.into_contents();
4791
4792 let err = alice
4793 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4794 .unwrap_err();
4795 assert!(
4796 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4797 if detail.contains("ExternalSenders")),
4798 "expected an ExternalSenders refusal, got {err:?}"
4799 );
4800 }
4801
4802 #[test]
4812 fn test_one_identity_cannot_hold_two_leaves_in_a_group() {
4813 let alice = create_test_manager("alice");
4814 let bob = create_test_manager("bob");
4815
4816 let gid = alice.create_group("Team").unwrap().group_id;
4817 let first = bob.generate_key_package().unwrap();
4818 alice
4819 .add_group_member(&gid, &addr("bob"), &first.key_package_data)
4820 .unwrap();
4821
4822 let second = bob.generate_key_package().unwrap();
4825 assert_ne!(first.package_id, second.package_id);
4826 let err = alice
4827 .add_group_member(&gid, &addr("bob"), &second.key_package_data)
4828 .unwrap_err();
4829 assert!(
4830 matches!(err, MlsError::AddMember(ref m) if m.contains("Duplicate signature key")),
4831 "expected MLS to refuse a duplicate signature key, got {err:?}"
4832 );
4833
4834 alice.remove_group_member(&gid, &addr("bob")).unwrap();
4836 assert!(!alice
4837 .get_group_info(&gid)
4838 .unwrap()
4839 .unwrap()
4840 .members
4841 .contains(&addr("bob")));
4842 }
4843
4844 #[test]
4860 fn test_external_join_proposal_is_refused_as_an_unsupported_sender() {
4861 let (alice, _bob, gid) = create_test_group_with_bob();
4862
4863 let outsider = create_test_manager("mallory");
4867 let kp_bytes = outsider.generate_key_package().unwrap().key_package_data;
4868 let key_package = KeyPackageIn::tls_deserialize_exact(&kp_bytes)
4869 .unwrap()
4870 .validate(alice.provider.crypto(), ProtocolVersion::Mls10)
4871 .unwrap();
4872 let signer = outsider.get_signer().unwrap();
4873
4874 let (mls_group_id, epoch) = {
4875 let group = alice.group_manager.load_group(&gid).unwrap().unwrap();
4876 (group.group_id().clone(), group.epoch())
4877 };
4878 let proposal =
4879 JoinProposal::new::<MlsStorageAdapter>(key_package, mls_group_id, epoch, &signer)
4880 .expect(
4881 "an external join proposal is well-formed; the SDK just declines to honour it",
4882 );
4883
4884 let err = alice
4885 .decrypt_from_group(
4886 &app_message(
4887 &gid,
4888 proposal.tls_serialize_detached().unwrap(),
4889 &addr("mallory"),
4890 ),
4891 &addr("mallory"),
4892 )
4893 .unwrap_err();
4894 assert!(
4895 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4896 if detail.contains("not a group member")),
4897 "expected an UnsupportedSender refusal, got {err:?}"
4898 );
4899 }
4900
4901 #[test]
4915 fn test_roster_read_counts_the_leaves_it_skips() {
4916 let (alice, _bob, gid) = create_test_group_with_bob();
4917
4918 let before = alice.get_group_info(&gid).unwrap().unwrap();
4919 assert_eq!(
4920 before.unproven_members, 0,
4921 "an honest group must report no unproven members: {:?}",
4922 before.members
4923 );
4924 let honest_members = before.members.len();
4925
4926 let impostor = Impostor::claiming(&addr("carol"), "an-attackers-key");
4929 add_member_bypassing_checks(&alice, &gid, &impostor.key_package);
4930
4931 let after = alice.get_group_info(&gid).unwrap().unwrap();
4932 assert_eq!(
4933 after.unproven_members, 1,
4934 "the skipped leaf must be counted, not only dropped"
4935 );
4936 assert_eq!(
4937 after.members.len(),
4938 honest_members,
4939 "the roster must be unchanged: {:?}",
4940 after.members
4941 );
4942 assert!(
4943 !after.members.contains(&addr("carol")),
4944 "roster surfaced the unproven member: {:?}",
4945 after.members
4946 );
4947 }
4948
4949 fn key_package_with_window(label: &str, lifetime: Lifetime) -> (String, Vec<u8>) {
4959 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4960 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
4961 let (keys, address) = test_identity(label);
4962 keys.store(provider.storage()).unwrap();
4963
4964 let credential = CredentialWithKey {
4965 credential: Credential::new(
4966 CredentialType::Basic,
4967 address.to_string().as_bytes().to_vec(),
4968 ),
4969 signature_key: keys.public().into(),
4970 };
4971 let bytes = KeyPackage::builder()
4972 .key_package_lifetime(lifetime)
4973 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
4974 .unwrap()
4975 .key_package()
4976 .tls_serialize_detached()
4977 .unwrap();
4978
4979 (address.to_string(), bytes)
4980 }
4981
4982 fn now_secs() -> u64 {
4983 std::time::SystemTime::now()
4984 .duration_since(std::time::UNIX_EPOCH)
4985 .unwrap()
4986 .as_secs()
4987 }
4988
4989 #[test]
4995 fn a_window_that_has_not_started_by_our_clock_is_reported_as_such() {
4996 let (manager, _) = create_addressed_manager("alice");
4997 let year = 365 * 24 * 60 * 60;
4998 let (peer, bytes) = key_package_with_window(
4999 "bob",
5000 Lifetime::init(
5001 now_secs() + year - 3600,
5002 now_secs() + year + 30 * 24 * 60 * 60,
5003 ),
5004 );
5005
5006 let err = manager
5007 .import_key_package(&peer, &bytes)
5008 .expect_err("a window that starts in a year is not valid now");
5009 assert!(
5010 matches!(
5011 err,
5012 MlsError::KeyPackageOutsideValidityWindow { expired: false }
5013 ),
5014 "refused for the wrong reason: {err:?}"
5015 );
5016 }
5017
5018 #[test]
5021 fn a_window_that_closed_by_our_clock_is_reported_as_such() {
5022 let (manager, _) = create_addressed_manager("alice");
5023 let day = 24 * 60 * 60;
5024 let (peer, bytes) = key_package_with_window(
5025 "bob",
5026 Lifetime::init(now_secs() - 60 * day, now_secs() - 30 * day),
5027 );
5028
5029 let err = manager
5030 .import_key_package(&peer, &bytes)
5031 .expect_err("a window that closed a month ago is not valid now");
5032 assert!(
5033 matches!(
5034 err,
5035 MlsError::KeyPackageOutsideValidityWindow { expired: true }
5036 ),
5037 "refused for the wrong reason: {err:?}"
5038 );
5039 }
5040
5041 #[test]
5045 fn only_the_window_refusal_changes_type() {
5046 let (manager, _) = create_addressed_manager("alice");
5047 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5048 let err = manager.import_key_package(&peer, &bytes).unwrap_err();
5049 assert!(matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")));
5050
5051 let err = manager
5052 .import_key_package(&peer, b"not a key package")
5053 .unwrap_err();
5054 assert!(matches!(err, MlsError::InvalidKeyPackage(_)), "{err:?}");
5055 }
5056
5057 #[test]
5060 fn a_year_long_validity_window_is_refused_at_import() {
5061 let (manager, _) = create_addressed_manager("alice");
5062
5063 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5064
5065 let err = manager
5066 .import_key_package(&peer, &bytes)
5067 .expect_err("a year-long window must be refused");
5068 assert!(
5069 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5070 "refused for the wrong reason: {err:?}"
5071 );
5072 }
5073
5074 #[test]
5080 fn a_wide_window_written_straight_into_the_store_is_refused_on_read() {
5081 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
5082 let (keys, address) = test_identity("alice");
5083 seed_identity(&storage, &keys);
5084 let manager = MlsManager::new(address.to_string(), Arc::clone(&storage)).unwrap();
5085
5086 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5087 storage
5088 .store(StorageKeyType::ContactKeyPackage.as_str(), &peer, &bytes)
5089 .unwrap();
5090
5091 let err = manager
5092 .get_contact_key_package(&peer)
5093 .expect_err("a cached year-long window must be refused too");
5094 assert!(
5095 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5096 "refused for the wrong reason: {err:?}"
5097 );
5098 }
5099
5100 #[test]
5107 fn our_own_key_package_states_the_lifetime_we_document() {
5108 let (manager, _) = create_addressed_manager("alice");
5109
5110 let bundle = manager.generate_key_package().unwrap();
5111 let key_package = KeyPackageIn::tls_deserialize_exact(bundle.key_package_data.as_slice())
5112 .unwrap()
5113 .validate(manager.provider.crypto(), ProtocolVersion::Mls10)
5114 .unwrap();
5115
5116 let window = key_package.life_time();
5117 assert_eq!(
5118 window.not_after() - window.not_before(),
5119 DEFAULT_KEY_PACKAGE_LIFETIME_SECS + LIFETIME_SKEW_MARGIN_SECS,
5120 "the window on the wire is not the lifetime this crate declares"
5121 );
5122 assert!(
5123 MlsManager::verify_lifetime_bound(&key_package).is_ok(),
5124 "this install mints a package it would refuse from a peer"
5125 );
5126 }
5127
5128 #[test]
5136 fn a_year_long_validity_window_is_refused_by_a_group_invite() {
5137 let alice = create_test_manager("alice");
5138 let group_id = alice.create_group("Test Group").unwrap().group_id;
5139
5140 let (bob, wide) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5141 let err = alice
5142 .add_group_member(&group_id, &bob, &wide)
5143 .expect_err("a year-long window must be refused by the group path too");
5144 assert!(
5145 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5146 "refused for the wrong reason: {err:?}"
5147 );
5148
5149 let bob_manager = create_test_manager("bob");
5152 let bob_kp = bob_manager.generate_key_package().unwrap();
5153 alice
5154 .add_group_member(&group_id, &bob, &bob_kp.key_package_data)
5155 .expect("a package inside the cap is admitted");
5156 }
5157
5158 #[test]
5161 fn a_window_exactly_at_the_cap_is_admitted_and_one_second_over_is_not() {
5162 let cap = MAX_ACCEPTED_KEY_PACKAGE_LIFETIME.as_secs();
5163 let (manager, _) = create_addressed_manager("alice");
5164
5165 let start = now_secs() - 10;
5168
5169 let (at_cap, bytes) = key_package_with_window("bob", Lifetime::init(start, start + cap));
5170 manager
5171 .import_key_package(&at_cap, &bytes)
5172 .expect("a window exactly at the cap is admitted");
5173
5174 let (over_cap, bytes) =
5175 key_package_with_window("carol", Lifetime::init(start, start + cap + 1));
5176 let err = manager
5177 .import_key_package(&over_cap, &bytes)
5178 .expect_err("one second over the cap must be refused");
5179 assert!(
5180 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5181 "refused for the wrong reason: {err:?}"
5182 );
5183 }
5184}