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 self.stamp_session_established(&welcome.group_id);
996
997 Ok(welcome)
998 }
999
1000 pub fn join_session(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1002 let info = self.session_manager.join_session(welcome)?;
1003 self.stamp_session_established(&welcome.group_id);
1004 Ok(info)
1005 }
1006
1007 pub fn session_established_at_ms(&self, other_user_id: &str) -> Result<Option<u64>> {
1015 let session_id = self.session_manager.get_session_id(other_user_id)?;
1016 Ok(self
1017 .load_group_metadata(&session_id)?
1018 .map(|metadata| metadata.created_at_ms))
1019 }
1020
1021 fn stamp_session_established(&self, session_id: &GroupId) {
1026 if let Err(e) = self.save_group_metadata(session_id, &GroupMetadata::new(None)) {
1027 warn!(session_id = %session_id, error = %e, "Failed to stamp session establishment time");
1028 }
1029 }
1030
1031 pub fn replace_session_with_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1038 let other_user_id = &welcome.inviter_id;
1039
1040 offline_protocol_core::validate_id_chars(other_user_id, "User ID")
1044 .map_err(|e| MlsError::InvalidUserId(e.to_string()))?;
1045
1046 self.session_manager.verify_welcome_slot(welcome)?;
1051
1052 let _ = self.clear_pending_welcome(other_user_id);
1054
1055 let key_type = StorageKeyType::ContactKeyPackage.as_str();
1057 let _ = self.storage.delete(key_type, other_user_id);
1058
1059 self.join_session(welcome)
1063 }
1064
1065 pub fn encrypt_for_user(
1067 &self,
1068 other_user_id: &str,
1069 plaintext: &[u8],
1070 ) -> Result<EncryptedMessage> {
1071 if !self.has_session(other_user_id)? {
1072 let welcome = self.create_session(other_user_id)?;
1073 warn!(
1074 other_user_id = %other_user_id,
1075 "Created new session - Welcome message needs to be sent"
1076 );
1077 let key_type = StorageKeyType::PendingWelcome.as_str();
1078 let welcome_data =
1079 serde_json::to_vec(&welcome).map_err(|e| MlsError::Serialization(e.to_string()))?;
1080 self.storage.store(key_type, other_user_id, &welcome_data)?;
1081 }
1082
1083 let signature_keys = self.get_signer()?;
1084 self.session_manager
1085 .encrypt_message(other_user_id, plaintext, &signature_keys)
1086 }
1087
1088 pub fn encrypt_for_existing_session(
1095 &self,
1096 other_user_id: &str,
1097 plaintext: &[u8],
1098 ) -> Result<EncryptedMessage> {
1099 let signature_keys = self.get_signer()?;
1100 self.session_manager
1101 .encrypt_message(other_user_id, plaintext, &signature_keys)
1102 }
1103
1104 pub fn get_pending_welcome(&self, other_user_id: &str) -> Result<Option<WelcomeMessage>> {
1106 let key_type = StorageKeyType::PendingWelcome.as_str();
1107 match self.storage.load(key_type, other_user_id)? {
1108 Some(data) => {
1109 let welcome: WelcomeMessage = serde_json::from_slice(&data)
1110 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1111 Ok(Some(welcome))
1112 }
1113 None => Ok(None),
1114 }
1115 }
1116
1117 pub fn clear_pending_welcome(&self, other_user_id: &str) -> Result<()> {
1119 let key_type = StorageKeyType::PendingWelcome.as_str();
1120 self.storage.delete(key_type, other_user_id)?;
1121 Ok(())
1122 }
1123
1124 pub fn decrypt_from_user(
1131 &self,
1132 encrypted: &EncryptedMessage,
1133 claimed_sender: &str,
1134 ) -> Result<Option<Vec<u8>>> {
1135 self.session_manager
1136 .decrypt_message(encrypted, claimed_sender)
1137 }
1138
1139 pub fn list_sessions(&self) -> Result<Vec<String>> {
1141 self.session_manager.list_sessions()
1142 }
1143
1144 pub fn delete_session(&self, other_user_id: &str) -> Result<()> {
1151 let session_id = self.session_manager.get_session_id(other_user_id)?;
1152 self.storage
1153 .delete(StorageKeyType::GroupMetadata.as_str(), session_id.as_str())?;
1154 self.session_manager.delete_session(other_user_id)
1155 }
1156
1157 pub fn create_group(&self, group_name: &str) -> Result<GroupInfo> {
1163 let group_id = GroupId::new(format!("group:{}", Uuid::new_v4()))?;
1164 let credential = self.get_credential()?;
1165 let signature_keys = self.get_signer()?;
1166
1167 let group = self
1168 .group_manager
1169 .create_group(&group_id, &credential, &signature_keys)?;
1170
1171 let metadata = GroupMetadata::new_with_creator(Some(group_name.to_string()), &self.user_id);
1173 self.save_group_metadata(&group_id, &metadata)?;
1174
1175 let mut info = self.group_manager.get_group_info(&group, &group_id);
1176 info.name = metadata.name;
1177 info.created_at_ms = metadata.created_at_ms;
1178 info.last_activity_ms = metadata.last_activity_ms;
1179
1180 info!(group_id = %group_id, name = %group_name, "Created new group");
1181 Ok(info)
1182 }
1183
1184 pub fn add_group_member(
1206 &self,
1207 group_id: &GroupId,
1208 invitee_user_id: &str,
1209 member_key_package: &[u8],
1210 ) -> Result<(WelcomeMessage, EncryptedMessage)> {
1211 let key_package = self.validate_peer_key_package(member_key_package)?;
1212
1213 Self::verify_credential_identity(&key_package, invitee_user_id)?;
1214 Self::verify_address_binding(&key_package, invitee_user_id)?;
1215 Self::verify_lifetime_bound(&key_package)?;
1216
1217 let mut group = self
1218 .group_manager
1219 .load_group(group_id)?
1220 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1221
1222 let signature_keys = self.get_signer()?;
1223 let (commit, welcome) =
1224 self.group_manager
1225 .add_member(&mut group, key_package, &signature_keys)?;
1226
1227 self.group_manager.save_group(group_id, &group)?;
1228
1229 let welcome_bytes = welcome
1230 .tls_serialize_detached()
1231 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1232
1233 let commit_bytes = commit
1234 .tls_serialize_detached()
1235 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1236
1237 let group_name = self.load_group_metadata(group_id)?.and_then(|m| m.name);
1239
1240 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
1241
1242 let welcome_msg = WelcomeMessage {
1243 group_id: group_id.clone(),
1244 welcome_data: welcome_bytes,
1245 inviter_id: self.user_id.clone(),
1246 group_name,
1247 timestamp_ms: now_ms,
1248 };
1249
1250 let commit_msg = EncryptedMessage {
1251 group_id: group_id.clone(),
1252 message_type: MlsMessageType::Commit,
1253 epoch: group.epoch().as_u64(),
1254 ciphertext: commit_bytes,
1255 sender_id: self.user_id.clone(),
1256 timestamp_ms: now_ms,
1257 };
1258
1259 Ok((welcome_msg, commit_msg))
1260 }
1261
1262 pub fn remove_group_member(
1289 &self,
1290 group_id: &GroupId,
1291 member_id: &str,
1292 ) -> Result<EncryptedMessage> {
1293 let mut group = self
1294 .group_manager
1295 .load_group(group_id)?
1296 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1297
1298 let member_indices: Vec<LeafNodeIndex> = group
1299 .members()
1300 .filter(|m| m.credential.serialized_content() == member_id.as_bytes())
1301 .map(|m| m.index)
1302 .collect();
1303
1304 if member_indices.is_empty() {
1305 return Err(MlsError::UserNotInGroup(member_id.to_string()));
1306 }
1307
1308 let signature_keys = self.get_signer()?;
1309 let commit =
1310 self.group_manager
1311 .remove_member(&mut group, &member_indices, &signature_keys)?;
1312
1313 self.group_manager.save_group(group_id, &group)?;
1314
1315 let ciphertext = commit
1316 .tls_serialize_detached()
1317 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1318
1319 Ok(EncryptedMessage {
1320 group_id: group_id.clone(),
1321 message_type: MlsMessageType::Commit,
1322 epoch: group.epoch().as_u64(),
1323 ciphertext,
1324 sender_id: self.user_id.clone(),
1325 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1326 })
1327 }
1328
1329 pub fn leave_group(&self, group_id: &GroupId) -> Result<()> {
1331 self.group_manager.delete_group(group_id)?;
1332 info!(group_id = %group_id, "Left group");
1333 Ok(())
1334 }
1335
1336 pub fn encrypt_for_group(
1338 &self,
1339 group_id: &GroupId,
1340 plaintext: &[u8],
1341 ) -> Result<EncryptedMessage> {
1342 let mut group = self
1343 .group_manager
1344 .load_group(group_id)?
1345 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1346
1347 let signature_keys = self.get_signer()?;
1348 let mls_message =
1349 self.group_manager
1350 .encrypt_message(&mut group, plaintext, &signature_keys)?;
1351
1352 self.group_manager.save_group(group_id, &group)?;
1353
1354 let ciphertext = mls_message
1355 .tls_serialize_detached()
1356 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1357
1358 Ok(EncryptedMessage {
1359 group_id: group_id.clone(),
1360 message_type: MlsMessageType::Application,
1361 epoch: group.epoch().as_u64(),
1362 ciphertext,
1363 sender_id: self.user_id.clone(),
1364 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1365 })
1366 }
1367
1368 pub fn decrypt_from_group(
1376 &self,
1377 encrypted: &EncryptedMessage,
1378 claimed_sender: &str,
1379 ) -> Result<Option<Vec<u8>>> {
1380 let mut group = self
1381 .group_manager
1382 .load_group(&encrypted.group_id)?
1383 .ok_or_else(|| MlsError::GroupNotFound(encrypted.group_id.to_string()))?;
1384
1385 let mls_message = MlsMessageIn::tls_deserialize_exact(&encrypted.ciphertext)
1386 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1387
1388 let result = self.group_manager.decrypt_message(
1389 &mut group,
1390 &encrypted.group_id,
1391 mls_message,
1392 claimed_sender,
1393 )?;
1394
1395 self.group_manager.save_group(&encrypted.group_id, &group)?;
1396
1397 Ok(result)
1398 }
1399
1400 pub fn join_group(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1402 if welcome.group_id.as_str().starts_with("session:") {
1414 return Err(MlsError::ReservedSessionNamespace {
1415 group_id: welcome.group_id.to_string(),
1416 });
1417 }
1418
1419 let mls_msg = MlsMessageIn::tls_deserialize_exact(&welcome.welcome_data)
1420 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1421
1422 let welcome_msg = match mls_msg.extract() {
1423 MlsMessageBodyIn::Welcome(w) => w,
1424 _ => {
1425 return Err(MlsError::WelcomeProcessing(
1426 "Not a Welcome message".to_string(),
1427 ))
1428 }
1429 };
1430
1431 let group = self
1432 .group_manager
1433 .join_group(welcome_msg, &welcome.group_id)?;
1434 let mut info = self.group_manager.get_group_info(&group, &welcome.group_id);
1435 info.name = welcome.group_name.clone();
1436
1437 info!(group_id = %welcome.group_id, "Joined group");
1438 Ok(info)
1439 }
1440
1441 pub fn list_groups(&self) -> Result<Vec<GroupId>> {
1443 let all_groups = self.group_manager.list_groups()?;
1444 Ok(all_groups
1445 .into_iter()
1446 .filter(|g| !g.as_str().starts_with("session:"))
1447 .collect())
1448 }
1449
1450 pub fn get_group_info(&self, group_id: &GroupId) -> Result<Option<GroupInfo>> {
1452 let group = match self.group_manager.load_group(group_id)? {
1453 Some(g) => g,
1454 None => return Ok(None),
1455 };
1456
1457 let mut info = self.group_manager.get_group_info(&group, group_id);
1458
1459 if let Some(metadata) = self.load_group_metadata(group_id)? {
1461 info.name = metadata.name;
1462 info.created_at_ms = metadata.created_at_ms;
1463 info.last_activity_ms = metadata.last_activity_ms;
1464 }
1465
1466 Ok(Some(info))
1467 }
1468
1469 pub fn has_group(&self, group_id: &GroupId) -> Result<bool> {
1478 Ok(self.group_manager.load_group(group_id)?.is_some())
1479 }
1480
1481 pub fn set_group_name(&self, group_id: &GroupId, name: &str) -> Result<()> {
1483 let mut metadata = self
1484 .load_group_metadata(group_id)?
1485 .unwrap_or_else(|| GroupMetadata::new(None));
1486 metadata.name = Some(name.to_string());
1487 metadata.touch();
1488 self.save_group_metadata(group_id, &metadata)
1489 }
1490
1491 pub fn get_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1493 self.load_group_metadata(group_id)
1494 }
1495
1496 pub fn set_group_custom_metadata(
1498 &self,
1499 group_id: &GroupId,
1500 key: &str,
1501 value: &str,
1502 ) -> Result<()> {
1503 let mut metadata = self
1504 .load_group_metadata(group_id)?
1505 .unwrap_or_else(|| GroupMetadata::new(None));
1506 metadata.custom.insert(key.to_string(), value.to_string());
1507 metadata.touch();
1508 self.save_group_metadata(group_id, &metadata)
1509 }
1510
1511 pub fn remove_group_custom_metadata(&self, group_id: &GroupId, key: &str) -> Result<()> {
1513 let mut metadata = self
1514 .load_group_metadata(group_id)?
1515 .unwrap_or_else(|| GroupMetadata::new(None));
1516 metadata.custom.remove(key);
1517 metadata.touch();
1518 self.save_group_metadata(group_id, &metadata)
1519 }
1520
1521 pub fn set_member_role(
1523 &self,
1524 group_id: &GroupId,
1525 user_id: &str,
1526 role: GroupRole,
1527 ) -> Result<()> {
1528 let mut metadata = self
1529 .load_group_metadata(group_id)?
1530 .unwrap_or_else(|| GroupMetadata::new(None));
1531 metadata.set_role(user_id, role);
1532 metadata.touch();
1533 self.save_group_metadata(group_id, &metadata)
1534 }
1535
1536 pub fn set_enforce_admin_commits(&mut self, enforce: bool) {
1545 self.group_manager.set_enforce_admin_commits(enforce);
1546 }
1547
1548 pub fn set_group_creator(&self, group_id: &GroupId, creator_id: &str) -> Result<()> {
1562 let mut metadata = self
1563 .load_group_metadata(group_id)?
1564 .unwrap_or_else(|| GroupMetadata::new(None));
1565 if metadata.created_by.is_some() {
1566 return Ok(());
1567 }
1568 metadata.created_by = Some(creator_id.to_string());
1569 metadata.touch();
1570 self.save_group_metadata(group_id, &metadata)
1571 }
1572
1573 pub fn remove_member_role(&self, group_id: &GroupId, user_id: &str) -> Result<()> {
1575 let mut metadata = self
1576 .load_group_metadata(group_id)?
1577 .unwrap_or_else(|| GroupMetadata::new(None));
1578 metadata.remove_role(user_id);
1579 metadata.touch();
1580 self.save_group_metadata(group_id, &metadata)
1581 }
1582
1583 pub fn decrypt(
1593 &self,
1594 encrypted: &EncryptedMessage,
1595 claimed_sender: &str,
1596 ) -> Result<Option<Vec<u8>>> {
1597 if encrypted.group_id.as_str().starts_with("session:") {
1598 self.decrypt_from_user(encrypted, claimed_sender)
1599 } else {
1600 self.decrypt_from_group(encrypted, claimed_sender)
1601 }
1602 }
1603
1604 pub fn process_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1606 if welcome.group_id.as_str().starts_with("session:") {
1607 self.join_session(welcome)
1608 } else {
1609 self.join_group(welcome)
1610 }
1611 }
1612}
1613
1614impl MlsManager {
1615 fn load_stored_key_package(&self, package_id: &str) -> Result<Option<KeyPackageBundle>> {
1621 let key_type = StorageKeyType::KeyPackage.as_str();
1622 let data = match self.storage.load(key_type, package_id)? {
1623 Some(data) => data,
1624 None => return Ok(None),
1625 };
1626
1627 let mut bundle = match serde_json::from_slice::<KeyPackageBundle>(&data) {
1628 Ok(bundle) => bundle,
1629 Err(_) => {
1630 let legacy_bundle = KeyPackageBundle::new(
1631 package_id.to_string(),
1632 self.user_id.clone(),
1633 data,
1634 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
1635 );
1636 let serialized = serde_json::to_vec(&legacy_bundle)
1637 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1638 self.storage.store(key_type, package_id, &serialized)?;
1639 legacy_bundle
1640 }
1641 };
1642
1643 if bundle.is_expired() {
1644 if bundle.expired_past_grace(KEY_PACKAGE_PURGE_GRACE_SECS) {
1651 self.purge_key_package_material(package_id, &bundle)?;
1652 }
1653 return Ok(None);
1654 }
1655
1656 let needs_backfill = bundle.provider_hash_ref.is_none();
1662 let Some(hash_ref) = self.bundle_hash_ref(&bundle) else {
1663 warn!(
1664 package_id = %package_id,
1665 "Key package bytes no longer parse as a key package, pruning stale entry"
1666 );
1667 self.storage.delete(key_type, package_id)?;
1668 return Ok(None);
1669 };
1670 if !self.provider_has_init_key(&hash_ref) {
1671 warn!(
1672 package_id = %package_id,
1673 "Key package private key no longer in provider storage, pruning stale entry"
1674 );
1675 self.storage.delete(key_type, package_id)?;
1676 return Ok(None);
1677 }
1678
1679 if needs_backfill {
1680 if let Ok(bytes) = hash_ref.tls_serialize_detached() {
1681 bundle.provider_hash_ref = Some(bytes);
1682 let serialized = serde_json::to_vec(&bundle)
1683 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1684 self.storage.store(key_type, package_id, &serialized)?;
1685 }
1686 }
1687
1688 Ok(Some(bundle))
1689 }
1690
1691 fn purge_key_package_material(
1706 &self,
1707 package_id: &str,
1708 bundle: &KeyPackageBundle,
1709 ) -> Result<()> {
1710 if let Some(hash_ref) = self.bundle_hash_ref(bundle) {
1711 use openmls_traits::storage::StorageProvider;
1712 if let Err(e) = self.provider.storage().delete_key_package(&hash_ref) {
1713 warn!(
1714 package_id = %package_id,
1715 error = %e,
1716 "Failed to delete a key package's private init key; keeping the record to retry"
1717 );
1718 return Ok(());
1719 }
1720 }
1721
1722 self.storage
1723 .delete(StorageKeyType::KeyPackage.as_str(), package_id)?;
1724 debug!(package_id = %package_id, "Purged key package and its init key");
1725 Ok(())
1726 }
1727
1728 fn key_package_hash_ref(&self, key_package_data: &[u8]) -> Option<KeyPackageRef> {
1735 let kp_in = KeyPackageIn::tls_deserialize_exact(key_package_data).ok()?;
1736 let kp: KeyPackage = kp_in
1737 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
1738 .ok()?;
1739 kp.hash_ref(self.provider.crypto()).ok()
1740 }
1741
1742 fn bundle_hash_ref(&self, bundle: &KeyPackageBundle) -> Option<KeyPackageRef> {
1746 bundle
1747 .provider_hash_ref
1748 .as_deref()
1749 .and_then(|bytes| KeyPackageRef::tls_deserialize_exact(bytes).ok())
1750 .or_else(|| self.key_package_hash_ref(&bundle.key_package_data))
1751 }
1752
1753 fn provider_has_init_key(&self, hash_ref: &KeyPackageRef) -> bool {
1757 use openmls_traits::storage::StorageProvider;
1758 let found: std::result::Result<Option<openmls::key_packages::KeyPackageBundle>, _> =
1759 self.provider.storage().key_package(hash_ref);
1760 matches!(found, Ok(Some(_)))
1761 }
1762
1763 #[cfg(test)]
1768 fn is_key_package_usable(&self, key_package_data: &[u8]) -> bool {
1769 self.key_package_hash_ref(key_package_data)
1770 .map(|hash_ref| self.provider_has_init_key(&hash_ref))
1771 .unwrap_or(false)
1772 }
1773
1774 fn load_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1776 let key_type = StorageKeyType::GroupMetadata.as_str();
1777 match self.storage.load(key_type, group_id.as_str())? {
1778 Some(data) => {
1779 let mut metadata: GroupMetadata = serde_json::from_slice(&data)
1780 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1781 if metadata.roles.is_empty()
1783 && metadata
1784 .custom
1785 .keys()
1786 .any(|k| k.starts_with(GroupMetadata::LEGACY_ROLE_KEY_PREFIX))
1787 {
1788 metadata.migrate_legacy_roles();
1789 if let Err(e) = self.save_group_metadata(group_id, &metadata) {
1791 warn!(group_id = %group_id.as_str(), error = %e, "Failed to persist legacy role migration");
1792 }
1793 }
1794 Ok(Some(metadata))
1795 }
1796 None => Ok(None),
1797 }
1798 }
1799
1800 fn save_group_metadata(&self, group_id: &GroupId, metadata: &GroupMetadata) -> Result<()> {
1802 let key_type = StorageKeyType::GroupMetadata.as_str();
1803 let data =
1804 serde_json::to_vec(metadata).map_err(|e| MlsError::Serialization(e.to_string()))?;
1805 self.storage.store(key_type, group_id.as_str(), &data)?;
1806 Ok(())
1807 }
1808}
1809
1810impl MlsManager {
1815 pub fn update_keys(&self, group_id: &GroupId) -> Result<EncryptedMessage> {
1824 use openmls::treesync::LeafNodeParameters;
1825
1826 let mut group = self
1827 .group_manager
1828 .load_group(group_id)?
1829 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1830
1831 let signature_keys = self.get_signer()?;
1832
1833 let bundle = group
1834 .self_update(
1835 &self.provider,
1836 &signature_keys,
1837 LeafNodeParameters::default(),
1838 )
1839 .map_err(|e| MlsError::OpenMls(format!("Self-update failed: {}", e)))?;
1840
1841 let (commit, _welcome, _group_info) = bundle.into_contents();
1842
1843 group
1844 .merge_pending_commit(&self.provider)
1845 .map_err(|e| MlsError::OpenMls(format!("Failed to merge self-update commit: {}", e)))?;
1846
1847 self.group_manager.save_group(group_id, &group)?;
1848
1849 if let Some(mut metadata) = self.load_group_metadata(group_id)? {
1851 metadata.touch();
1852 self.save_group_metadata(group_id, &metadata)?;
1853 }
1854
1855 let ciphertext = commit
1856 .tls_serialize_detached()
1857 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1858
1859 debug!(group_id = %group_id, epoch = %group.epoch().as_u64(), "Updated group keys");
1860
1861 Ok(EncryptedMessage {
1862 group_id: group_id.clone(),
1863 message_type: MlsMessageType::Commit,
1864 epoch: group.epoch().as_u64(),
1865 ciphertext,
1866 sender_id: self.user_id.clone(),
1867 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1868 })
1869 }
1870
1871 pub fn ensure_min_key_packages(&self, min: usize) -> Result<usize> {
1896 let min = min.min(MAX_PUSH_KEY_PACKAGES);
1897 let key_type = StorageKeyType::KeyPackage.as_str();
1898 let package_ids = self.storage.list_keys(key_type)?;
1899
1900 let mut valid_count = 0;
1902 for package_id in &package_ids {
1903 if self
1904 .load_stored_key_package(package_id)?
1905 .is_some_and(|bundle| !bundle.withheld_from_hand_out())
1906 {
1907 valid_count += 1;
1908 }
1909 }
1910
1911 let to_generate = min.saturating_sub(valid_count);
1913 for _ in 0..to_generate {
1914 self.generate_key_package()?;
1915 valid_count += 1;
1916 }
1917
1918 debug!(
1919 valid_count = valid_count,
1920 generated = to_generate,
1921 "Ensured minimum key packages"
1922 );
1923
1924 Ok(valid_count)
1925 }
1926
1927 pub fn count_valid_key_packages(&self) -> Result<usize> {
1934 let key_type = StorageKeyType::KeyPackage.as_str();
1935 let package_ids = self.storage.list_keys(key_type)?;
1936
1937 let mut count = 0;
1938 for package_id in package_ids {
1939 if self.load_stored_key_package(&package_id)?.is_some() {
1940 count += 1;
1941 }
1942 }
1943
1944 Ok(count)
1945 }
1946
1947 pub fn get_identity_public_key(&self) -> Result<Vec<u8>> {
1956 let credential = self.get_credential()?;
1957 Ok(credential.signature_key.as_slice().to_vec())
1958 }
1959
1960 pub fn sign_data(&self, data: &[u8]) -> Result<Vec<u8>> {
1973 use openmls_traits::signatures::Signer;
1974
1975 let signer = self.get_signer()?;
1976 let signature = signer
1977 .sign(data)
1978 .map_err(|e| MlsError::Signing(format!("Failed to sign data: {:?}", e)))?;
1979 Ok(signature.as_slice().to_vec())
1980 }
1981
1982 pub fn verify_signature(public_key: &[u8], data: &[u8], signature: &[u8]) -> Result<bool> {
1994 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
1995
1996 let verifying_key = VerifyingKey::try_from(public_key).map_err(|e| {
1998 MlsError::InvalidPublicKey(format!("Invalid Ed25519 public key: {}", e))
1999 })?;
2000
2001 let sig = Signature::try_from(signature).map_err(|e| {
2003 MlsError::VerificationFailed(format!("Invalid signature format: {}", e))
2004 })?;
2005
2006 match verifying_key.verify(data, &sig) {
2008 Ok(()) => Ok(true),
2009 Err(_) => Ok(false),
2010 }
2011 }
2012
2013 pub fn derive_address(public_key: &[u8]) -> Result<Address> {
2039 offline_protocol_sealed::derive_address(public_key).map_err(MlsError::from)
2040 }
2041
2042 #[deprecated(
2062 note = "use derive_address; this returns the self-certifying off1… address, no longer base58"
2063 )]
2064 pub fn derive_user_id_from_public_key(public_key: &[u8]) -> String {
2065 use sha2::{Digest, Sha256};
2066
2067 let hash = Sha256::digest(public_key);
2068 let mut truncated = [0u8; Address::HASH_LEN];
2069 truncated.copy_from_slice(&hash[..Address::HASH_LEN]);
2070 Address::from_hash_bytes(truncated).to_string()
2071 }
2072}
2073
2074#[cfg(feature = "test-utils")]
2083impl MlsManager {
2084 pub fn seat_forged_leaf_for_testing(
2098 &self,
2099 group_id: &GroupId,
2100 impersonated: &str,
2101 ) -> Result<Vec<u8>> {
2102 let forged_storage: Arc<dyn MlsStorage> = Arc::new(crate::storage::InMemoryStorage::new());
2103 let forged_provider = MlsProvider::new(MlsStorageAdapter::new(forged_storage));
2104 let forged_keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
2105 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
2106 forged_keys
2107 .store(forged_provider.storage())
2108 .map_err(|e| MlsError::CryptoGeneration(format!("storing forged key: {:?}", e)))?;
2109
2110 let credential = CredentialWithKey {
2111 credential: Credential::new(CredentialType::Basic, impersonated.as_bytes().to_vec()),
2112 signature_key: forged_keys.public().into(),
2113 };
2114 let key_package = KeyPackage::builder()
2115 .build(
2116 DEFAULT_CIPHERSUITE,
2117 &forged_provider,
2118 &forged_keys,
2119 credential,
2120 )
2121 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?
2122 .key_package()
2123 .tls_serialize_detached()
2124 .map_err(|e| MlsError::Serialization(e.to_string()))?;
2125
2126 let validated = KeyPackageIn::tls_deserialize_exact(&key_package)
2127 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?
2128 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
2129 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
2130
2131 let mut group = self
2132 .group_manager
2133 .load_group(group_id)?
2134 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
2135 let signer = self.get_signer()?;
2136 let (commit, _welcome) = self
2137 .group_manager
2138 .add_member(&mut group, validated, &signer)?;
2139 self.group_manager.save_group(group_id, &group)?;
2140
2141 commit
2142 .tls_serialize_detached()
2143 .map_err(|e| MlsError::Serialization(e.to_string()))
2144 }
2145
2146 pub fn key_package_with_window_for_testing(
2154 claimed: &str,
2155 not_before: u64,
2156 not_after: u64,
2157 ) -> Result<Vec<u8>> {
2158 let storage: Arc<dyn MlsStorage> = Arc::new(crate::storage::InMemoryStorage::new());
2159 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
2160 let keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
2161 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
2162 keys.store(provider.storage())
2163 .map_err(|e| MlsError::CryptoGeneration(format!("storing key: {:?}", e)))?;
2164 let credential = CredentialWithKey {
2165 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
2166 signature_key: keys.public().into(),
2167 };
2168 KeyPackage::builder()
2169 .key_package_lifetime(Lifetime::init(not_before, not_after))
2170 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
2171 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?
2172 .key_package()
2173 .tls_serialize_detached()
2174 .map_err(|e| MlsError::Serialization(e.to_string()))
2175 }
2176}
2177
2178pub fn is_mls_framed(bytes: &[u8]) -> bool {
2187 MlsMessageIn::tls_deserialize_exact(bytes).is_ok()
2188}
2189
2190#[cfg(test)]
2191mod tests {
2192 use super::*;
2193 use crate::storage::InMemoryStorage;
2194
2195 fn create_test_manager(label: &str) -> MlsManager {
2204 create_addressed_manager(label).0
2205 }
2206
2207 fn addr(label: &str) -> String {
2209 test_identity(label).1.to_string()
2210 }
2211
2212 fn slot(a: &str, b: &str) -> GroupId {
2218 GroupId::for_session(&addr(a), &addr(b)).unwrap()
2219 }
2220
2221 pub(crate) fn test_identity(label: &str) -> (SignatureKeyPair, Address) {
2229 use sha2::{Digest, Sha256};
2230
2231 let seed: [u8; 32] = Sha256::digest(label.as_bytes()).into();
2232 let signing = ed25519_dalek::SigningKey::from_bytes(&seed);
2233 let public = signing.verifying_key().to_bytes().to_vec();
2234 let address = MlsManager::derive_address(&public).unwrap();
2235 let keys = SignatureKeyPair::from_raw(
2236 DEFAULT_CIPHERSUITE.signature_algorithm(),
2237 signing.to_bytes().to_vec(),
2238 public,
2239 );
2240 (keys, address)
2241 }
2242
2243 pub(crate) fn seed_identity(storage: &Arc<dyn MlsStorage>, keys: &SignatureKeyPair) {
2246 storage
2247 .store(
2248 StorageKeyType::Identity.as_str(),
2249 "key_pair",
2250 &serde_json::to_vec(keys).unwrap(),
2251 )
2252 .unwrap();
2253 }
2254
2255 fn create_addressed_manager(label: &str) -> (MlsManager, Address) {
2258 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2259 let (keys, address) = test_identity(label);
2260 seed_identity(&storage, &keys);
2261 let manager = MlsManager::new(address.to_string(), storage).unwrap();
2262 (manager, address)
2263 }
2264
2265 fn create_test_manager_with_storage(user_id: &str) -> (MlsManager, Arc<InMemoryStorage>) {
2269 let storage = Arc::new(InMemoryStorage::new());
2270 let manager = MlsManager::new(user_id, storage.clone()).unwrap();
2271 (manager, storage)
2272 }
2273
2274 fn expire_package(storage: &InMemoryStorage, package_id: &str, secs_ago: u64) {
2276 let key_type = StorageKeyType::KeyPackage.as_str();
2277 let data = storage
2278 .load(key_type, package_id)
2279 .unwrap()
2280 .expect("package to age is not in storage");
2281 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2282 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
2283 bundle.expires_at_ms = now_ms - (secs_ago * 1000);
2284 storage
2285 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2286 .unwrap();
2287 }
2288
2289 fn init_key_present(manager: &MlsManager, bundle: &KeyPackageBundle) -> bool {
2291 manager.is_key_package_usable(&bundle.key_package_data)
2292 }
2293
2294 fn strip_cached_ref(storage: &InMemoryStorage, package_id: &str) {
2297 let key_type = StorageKeyType::KeyPackage.as_str();
2298 let data = storage
2299 .load(key_type, package_id)
2300 .unwrap()
2301 .expect("package to strip is not in storage");
2302 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2303 bundle.provider_hash_ref = None;
2304 storage
2305 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2306 .unwrap();
2307 }
2308
2309 #[test]
2314 fn test_minted_package_carries_its_provider_hash_ref() {
2315 let manager = create_test_manager("alice");
2316 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2317
2318 let cached = bundle
2319 .provider_hash_ref
2320 .as_deref()
2321 .expect("mint must stamp the provider hash ref");
2322 let derived = manager
2323 .key_package_hash_ref(&bundle.key_package_data)
2324 .expect("freshly minted package bytes must parse")
2325 .tls_serialize_detached()
2326 .unwrap();
2327 assert_eq!(
2328 cached,
2329 &derived[..],
2330 "cached ref disagrees with the one derived from the package bytes"
2331 );
2332 }
2333
2334 #[test]
2337 fn test_record_without_cached_ref_is_backfilled_on_first_load() {
2338 let (manager, storage) = create_test_manager_with_storage("alice");
2339 let minted = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2340 strip_cached_ref(&storage, &minted.package_id);
2341
2342 let loaded = manager
2343 .key_package_by_id(&minted.package_id)
2344 .unwrap()
2345 .expect("a record without a cached ref must still load");
2346 assert_eq!(
2347 loaded.provider_hash_ref, minted.provider_hash_ref,
2348 "load must hand back a bundle stamped with the same ref the mint computed"
2349 );
2350
2351 let stored: KeyPackageBundle = serde_json::from_slice(
2352 &storage
2353 .load(StorageKeyType::KeyPackage.as_str(), &minted.package_id)
2354 .unwrap()
2355 .unwrap(),
2356 )
2357 .unwrap();
2358 assert_eq!(
2359 stored.provider_hash_ref, minted.provider_hash_ref,
2360 "the backfilled ref must be persisted, not only returned"
2361 );
2362 }
2363
2364 #[test]
2367 fn test_purge_destroys_init_key_for_a_record_without_cached_ref() {
2368 let (manager, storage) = create_test_manager_with_storage("alice");
2369 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2370 strip_cached_ref(&storage, &bundle.package_id);
2371 expire_package(
2372 &storage,
2373 &bundle.package_id,
2374 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2375 );
2376
2377 assert!(manager
2378 .key_package_by_id(&bundle.package_id)
2379 .unwrap()
2380 .is_none());
2381 assert!(
2382 !init_key_present(&manager, &bundle),
2383 "the private init key outlived a pre-cache package"
2384 );
2385 }
2386
2387 #[test]
2392 fn test_expired_package_is_withdrawn_but_its_init_key_survives_the_grace_window() {
2393 let (manager, storage) = create_test_manager_with_storage("alice");
2394 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2395
2396 expire_package(&storage, &bundle.package_id, 60);
2397
2398 assert!(
2399 manager
2400 .key_package_by_id(&bundle.package_id)
2401 .unwrap()
2402 .is_none(),
2403 "an expired package must not be handed out"
2404 );
2405 assert!(
2406 init_key_present(&manager, &bundle),
2407 "the init key was destroyed on the expiry edge, breaking a late Welcome"
2408 );
2409
2410 let replacement = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2412 assert_ne!(replacement.package_id, bundle.package_id);
2413 }
2414
2415 #[test]
2420 fn test_package_past_the_grace_window_has_its_init_key_destroyed() {
2421 let (manager, storage) = create_test_manager_with_storage("alice");
2422 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2423 assert!(init_key_present(&manager, &bundle));
2424
2425 expire_package(
2426 &storage,
2427 &bundle.package_id,
2428 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2429 );
2430
2431 assert!(manager
2433 .key_package_by_id(&bundle.package_id)
2434 .unwrap()
2435 .is_none());
2436
2437 assert!(
2438 !init_key_present(&manager, &bundle),
2439 "the private init key outlived its package"
2440 );
2441 assert!(
2442 storage
2443 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2444 .unwrap()
2445 .is_none(),
2446 "the bundle record outlived its key material"
2447 );
2448 }
2449
2450 #[test]
2454 fn test_delete_key_package_destroys_the_init_key() {
2455 let manager = create_test_manager("alice");
2456 let bundle = manager.generate_publication_key_package().unwrap();
2457 assert!(init_key_present(&manager, &bundle));
2458
2459 manager.delete_key_package(&bundle.package_id).unwrap();
2460
2461 assert!(
2462 !init_key_present(&manager, &bundle),
2463 "delete_key_package left the private init key in provider storage"
2464 );
2465 }
2466
2467 #[test]
2472 fn test_delete_key_package_destroys_the_init_key_of_a_legacy_record() {
2473 let (manager, storage) = create_test_manager_with_storage("alice");
2474 let bundle = manager.generate_publication_key_package().unwrap();
2475 storage
2477 .store(
2478 StorageKeyType::KeyPackage.as_str(),
2479 &bundle.package_id,
2480 &bundle.key_package_data,
2481 )
2482 .unwrap();
2483
2484 manager.delete_key_package(&bundle.package_id).unwrap();
2485
2486 assert!(
2487 !init_key_present(&manager, &bundle),
2488 "a legacy record's private init key survived its deletion"
2489 );
2490 assert!(
2491 storage
2492 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2493 .unwrap()
2494 .is_none(),
2495 "the legacy record outlived its key material"
2496 );
2497 }
2498
2499 #[test]
2503 fn test_delete_key_package_removes_an_unreadable_record() {
2504 let (manager, storage) = create_test_manager_with_storage("alice");
2505 storage
2506 .store(
2507 StorageKeyType::KeyPackage.as_str(),
2508 "junk",
2509 b"not a package",
2510 )
2511 .unwrap();
2512
2513 manager.delete_key_package("junk").unwrap();
2514
2515 assert!(storage
2516 .load(StorageKeyType::KeyPackage.as_str(), "junk")
2517 .unwrap()
2518 .is_none());
2519 }
2520
2521 #[test]
2527 fn test_ensure_min_key_packages_is_capped_at_the_pool_ceiling() {
2528 let manager = create_test_manager("alice");
2529
2530 let count = manager
2531 .ensure_min_key_packages(MAX_PUSH_KEY_PACKAGES + 50)
2532 .unwrap();
2533
2534 assert_eq!(count, MAX_PUSH_KEY_PACKAGES);
2535 assert_eq!(
2536 manager.count_valid_key_packages().unwrap(),
2537 MAX_PUSH_KEY_PACKAGES,
2538 "pre-filling minted past the ceiling the push path enforces"
2539 );
2540 }
2541
2542 #[test]
2543 fn test_manager_creation() {
2544 let manager = create_test_manager("alice");
2545 assert_eq!(manager.user_id(), addr("alice"));
2546 }
2547
2548 #[test]
2549 fn test_key_package_generation() {
2550 let manager = create_test_manager("alice");
2551 let package = manager.generate_key_package().unwrap();
2552
2553 assert_eq!(package.user_id, addr("alice"));
2554 assert!(!package.key_package_data.is_empty());
2555 assert!(!package.is_expired());
2556 }
2557
2558 #[test]
2563 fn test_get_or_create_never_returns_a_publication_package() {
2564 let manager = create_test_manager("alice");
2565 let reserved = manager.generate_publication_key_package().unwrap();
2566 assert!(reserved.reserved_for_publication);
2567
2568 let pushed = manager.get_or_create_key_package().unwrap();
2569 assert_ne!(
2570 pushed.package_id, reserved.package_id,
2571 "the push path took the package a published record is standing on"
2572 );
2573 assert!(!pushed.reserved_for_publication);
2574
2575 let pushed_again = manager.get_or_create_key_package().unwrap();
2578 assert_eq!(pushed.package_id, pushed_again.package_id);
2579 }
2580
2581 #[test]
2585 fn test_key_package_by_id_reports_presence() {
2586 let manager = create_test_manager("alice");
2587 let bundle = manager.generate_publication_key_package().unwrap();
2588
2589 let found = manager.key_package_by_id(&bundle.package_id).unwrap();
2590 assert!(found.is_some());
2591 assert_eq!(found.unwrap().package_id, bundle.package_id);
2592
2593 assert!(manager
2594 .key_package_by_id("no-such-package")
2595 .unwrap()
2596 .is_none());
2597 }
2598
2599 #[test]
2602 fn test_publication_packages_are_distinct_per_slot() {
2603 let manager = create_test_manager("alice");
2604 let a = manager.generate_publication_key_package().unwrap();
2605 let b = manager.generate_publication_key_package().unwrap();
2606 assert_ne!(a.package_id, b.package_id);
2607 assert_ne!(a.key_package_data, b.key_package_data);
2608 }
2609
2610 #[test]
2611 fn test_get_or_create_key_package() {
2612 let manager = create_test_manager("alice");
2613 let pkg1 = manager.get_or_create_key_package().unwrap();
2614 let pkg2 = manager.get_or_create_key_package().unwrap();
2615 assert_eq!(pkg1.package_id, pkg2.package_id);
2619 }
2620
2621 #[test]
2631 fn test_push_path_gives_each_peer_its_own_key_package() {
2632 let manager = create_test_manager("alice");
2633
2634 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap();
2635 let for_carol = manager.take_push_key_package(&addr("carol")).unwrap();
2636
2637 assert_ne!(
2638 for_bob.bundle.package_id, for_carol.bundle.package_id,
2639 "two peers were advertised the same key package"
2640 );
2641 assert_ne!(
2642 for_bob.bundle.key_package_data, for_carol.bundle.key_package_data,
2643 "two peers were advertised the same init key"
2644 );
2645 assert!(!for_bob.pool_exhausted && !for_carol.pool_exhausted);
2646 assert_eq!(
2647 for_bob.bundle.assigned_peer.as_deref(),
2648 Some(addr("bob").as_str())
2649 );
2650 assert_eq!(
2651 for_carol.bundle.assigned_peer.as_deref(),
2652 Some(addr("carol").as_str())
2653 );
2654 }
2655
2656 #[test]
2661 fn test_push_path_reuses_one_package_for_repeat_pushes_to_a_peer() {
2662 let manager = create_test_manager("alice");
2663
2664 let first = manager.take_push_key_package(&addr("bob")).unwrap();
2665 let second = manager.take_push_key_package(&addr("bob")).unwrap();
2666 let third = manager.take_push_key_package(&addr("bob")).unwrap();
2667
2668 assert_eq!(first.bundle.package_id, second.bundle.package_id);
2669 assert_eq!(first.bundle.package_id, third.bundle.package_id);
2670 assert_eq!(
2671 manager.count_valid_key_packages().unwrap(),
2672 1,
2673 "repeat pushes to one peer minted extra key material"
2674 );
2675 }
2676
2677 #[test]
2682 fn test_push_path_mints_a_fresh_package_after_the_peer_consumed_one() {
2683 let alice = create_test_manager("alice");
2684 let bob = create_test_manager("bob");
2685
2686 let advertised = alice.take_push_key_package(&addr("bob")).unwrap().bundle;
2687 bob.import_key_package(&addr("alice"), &advertised.key_package_data)
2688 .unwrap();
2689 let welcome = bob.create_session(&addr("alice")).unwrap();
2690 alice.join_session(&welcome).unwrap();
2691
2692 let next = alice.take_push_key_package(&addr("bob")).unwrap();
2693 assert_ne!(
2694 next.bundle.package_id, advertised.package_id,
2695 "a consumed package was advertised again"
2696 );
2697 assert!(!next.pool_exhausted);
2698 }
2699
2700 #[test]
2705 fn test_two_peers_can_both_establish_sessions_after_being_pushed_to() {
2706 let alice = create_test_manager("alice");
2707 let bob = create_test_manager("bob");
2708 let carol = create_test_manager("carol");
2709
2710 for (peer, manager) in [(addr("bob"), &bob), (addr("carol"), &carol)] {
2711 let advertised = alice.take_push_key_package(&peer).unwrap().bundle;
2712 manager
2713 .import_key_package(&addr("alice"), &advertised.key_package_data)
2714 .unwrap();
2715 }
2716
2717 let from_bob = bob.create_session(&addr("alice")).unwrap();
2720 let from_carol = carol.create_session(&addr("alice")).unwrap();
2721
2722 alice.join_session(&from_bob).unwrap();
2723 alice
2724 .join_session(&from_carol)
2725 .expect("second peer's Welcome must still be processable");
2726
2727 assert!(alice.has_session(&addr("bob")).unwrap());
2728 assert!(alice.has_session(&addr("carol")).unwrap());
2729 }
2730
2731 #[test]
2734 fn test_push_path_never_takes_a_publication_package() {
2735 let manager = create_test_manager("alice");
2736 let reserved = manager.generate_publication_key_package().unwrap();
2737
2738 for peer in [addr("bob"), addr("carol"), addr("dave")] {
2739 let taken = manager.take_push_key_package(&peer).unwrap();
2740 assert_ne!(
2741 taken.bundle.package_id, reserved.package_id,
2742 "the push path took the package a published record stands on"
2743 );
2744 }
2745 }
2746
2747 #[test]
2751 fn test_get_or_create_never_returns_a_peer_assigned_package() {
2752 let manager = create_test_manager("alice");
2753 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2754
2755 let unclaimed = manager.get_or_create_key_package().unwrap();
2756 assert_ne!(unclaimed.package_id, for_bob.package_id);
2757 assert!(unclaimed.assigned_peer.is_none());
2758 }
2759
2760 fn record_present(storage: &InMemoryStorage, package_id: &str) -> bool {
2762 storage
2763 .load(StorageKeyType::KeyPackage.as_str(), package_id)
2764 .unwrap()
2765 .is_some()
2766 }
2767
2768 #[test]
2772 fn test_marking_synced_keeps_the_record_and_the_init_key() {
2773 let (mut manager, storage) = create_test_manager_with_storage("alice");
2774 let bundle = manager.generate_key_package().unwrap();
2775
2776 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2777
2778 assert!(
2779 record_present(&storage, &bundle.package_id),
2780 "marking synced deleted the record, so nothing can ever expire the key"
2781 );
2782 assert!(
2783 init_key_present(&manager, &bundle),
2784 "marking synced destroyed the init key a Welcome from the upload needs"
2785 );
2786 let reread = manager
2787 .key_package_by_id(&bundle.package_id)
2788 .unwrap()
2789 .expect("a synced package is still live");
2790 assert!(reread.synced);
2791 }
2792
2793 #[test]
2797 fn test_unclaimed_synced_package_has_its_init_key_destroyed_past_the_grace_window() {
2798 let (mut manager, storage) = create_test_manager_with_storage("alice");
2799 let bundle = manager.generate_key_package().unwrap();
2800 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2801
2802 expire_package(
2803 &storage,
2804 &bundle.package_id,
2805 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2806 );
2807 assert!(manager
2808 .key_package_by_id(&bundle.package_id)
2809 .unwrap()
2810 .is_none());
2811
2812 assert!(
2813 !init_key_present(&manager, &bundle),
2814 "a synced package's init key outlived its grace window"
2815 );
2816 assert!(!record_present(&storage, &bundle.package_id));
2817 }
2818
2819 #[test]
2822 fn test_synced_package_is_withheld_from_both_hand_out_paths() {
2823 let mut manager = create_test_manager("alice");
2824 let synced = manager.generate_key_package().unwrap();
2825 manager.mark_key_package_synced(&synced.package_id).unwrap();
2826
2827 let pushed = manager.take_push_key_package(&addr("bob")).unwrap();
2828 assert_ne!(pushed.bundle.package_id, synced.package_id);
2829 assert!(!pushed.pool_exhausted);
2830
2831 let peerless = manager.get_or_create_key_package().unwrap();
2832 assert_ne!(peerless.package_id, synced.package_id);
2833 }
2834
2835 #[test]
2839 fn test_pending_list_holds_only_packages_the_application_may_publish() {
2840 let mut manager = create_test_manager("alice");
2841 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2844 let plain = manager.generate_key_package().unwrap();
2845 let reserved = manager.generate_publication_key_package().unwrap();
2846 let synced = manager.generate_key_package().unwrap();
2847 manager.mark_key_package_synced(&synced.package_id).unwrap();
2848
2849 let pending: Vec<String> = manager
2850 .get_pending_key_packages()
2851 .unwrap()
2852 .into_iter()
2853 .map(|b| b.package_id)
2854 .collect();
2855
2856 assert!(pending.contains(&plain.package_id));
2857 assert!(
2858 !pending.contains(&reserved.package_id),
2859 "lists a slot package"
2860 );
2861 assert!(
2862 !pending.contains(&for_bob.package_id),
2863 "lists a peer's package"
2864 );
2865 assert!(
2866 !pending.contains(&synced.package_id),
2867 "lists a synced package"
2868 );
2869 }
2870
2871 #[test]
2875 fn test_ensure_min_does_not_count_a_synced_package_toward_the_pool() {
2876 let mut manager = create_test_manager("alice");
2877 let synced = manager.generate_key_package().unwrap();
2878 manager.mark_key_package_synced(&synced.package_id).unwrap();
2879
2880 assert_eq!(manager.ensure_min_key_packages(1).unwrap(), 1);
2881 assert_eq!(
2882 manager.count_valid_key_packages().unwrap(),
2883 2,
2884 "a replacement was minted beside the synced package"
2885 );
2886 }
2887
2888 #[test]
2891 fn test_welcome_built_against_a_synced_package_still_opens() {
2892 let mut alice = create_test_manager("alice");
2893 let bob = create_test_manager("bob");
2894 let uploaded = alice.generate_key_package().unwrap();
2895 alice.mark_key_package_synced(&uploaded.package_id).unwrap();
2896
2897 bob.import_key_package(&addr("alice"), &uploaded.key_package_data)
2898 .unwrap();
2899 let welcome = bob.create_session(&addr("alice")).unwrap();
2900
2901 alice
2902 .join_session(&welcome)
2903 .expect("a Welcome against a synced package must open");
2904 assert!(alice.has_session(&addr("bob")).unwrap());
2905 }
2906
2907 #[test]
2910 fn test_marking_a_legacy_record_upgrades_and_marks_it() {
2911 let (mut manager, storage) = create_test_manager_with_storage("alice");
2912 let bundle = manager.generate_key_package().unwrap();
2913 storage
2914 .store(
2915 StorageKeyType::KeyPackage.as_str(),
2916 &bundle.package_id,
2917 &bundle.key_package_data,
2918 )
2919 .unwrap();
2920
2921 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2922
2923 let reread = manager
2924 .key_package_by_id(&bundle.package_id)
2925 .unwrap()
2926 .expect("the legacy package is still live");
2927 assert!(reread.synced);
2928 assert!(init_key_present(&manager, &bundle));
2929 }
2930
2931 #[test]
2934 fn test_marking_an_unknown_or_consumed_package_is_a_no_op() {
2935 let mut alice = create_test_manager("alice");
2936 alice.mark_key_package_synced("no-such-package").unwrap();
2937
2938 let bob = create_test_manager("bob");
2939 let advertised = alice.take_push_key_package(&addr("bob")).unwrap().bundle;
2940 bob.import_key_package(&addr("alice"), &advertised.key_package_data)
2941 .unwrap();
2942 let welcome = bob.create_session(&addr("alice")).unwrap();
2943 alice.join_session(&welcome).unwrap();
2944
2945 alice
2946 .mark_key_package_synced(&advertised.package_id)
2947 .unwrap();
2948 assert!(alice
2949 .key_package_by_id(&advertised.package_id)
2950 .unwrap()
2951 .is_none());
2952 }
2953
2954 #[test]
2969 fn only_the_known_sites_delete_a_key_package_record_alone() {
2970 let allowed = ["load_stored_key_package", "purge_key_package_material"];
2971 let (checked, offenders) = record_only_key_package_deletes(include_str!("manager.rs"));
2972
2973 assert!(
2974 checked >= 8,
2975 "found only {checked} key package functions; the scan is broken, not the code"
2976 );
2977 let offenders: Vec<_> = offenders
2978 .into_iter()
2979 .filter(|name| !allowed.contains(&name.as_str()))
2980 .collect();
2981 assert!(
2982 offenders.is_empty(),
2983 "record-only delete of a key package in {offenders:?}. Destroy the init \
2984 key too (purge_key_package_material), or keep the record"
2985 );
2986 }
2987
2988 #[test]
2993 fn the_record_delete_scanner_sees_every_function_header_and_crlf() {
2994 let source = [
2995 "impl MlsManager {",
2996 " fn purge_key_package_material(&self) {",
2997 " let t = StorageKeyType::KeyPackage.as_str();",
2998 " self.storage.delete(t, id)?;",
2999 " }",
3000 " pub(crate) fn hidden_crate(&self) {",
3001 " self.storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3002 " }",
3003 " fn load_stored_key_package(&self) {}",
3004 " pub async fn hidden_async(&self) {",
3005 " self.storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3006 " }",
3007 "}",
3008 "fn hidden_free() {",
3009 " storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3010 "}",
3011 "#[cfg(test)]",
3012 "mod tests {",
3013 " fn in_tests() {",
3014 " storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3015 " }",
3016 "}",
3017 ]
3018 .join("\r\n");
3019
3020 let (checked, offenders) = record_only_key_package_deletes(&source);
3021
3022 assert_eq!(checked, 4, "the test module is out of scope");
3023 assert_eq!(
3024 offenders,
3025 [
3026 "purge_key_package_material",
3027 "hidden_crate",
3028 "hidden_async",
3029 "hidden_free"
3030 ]
3031 );
3032 }
3033
3034 fn record_only_key_package_deletes(source: &str) -> (usize, Vec<String>) {
3039 let lines: Vec<&str> = source.lines().collect();
3040 let test_module = lines
3041 .windows(2)
3042 .position(|pair| pair[0].trim() == "#[cfg(test)]" && pair[1].starts_with("mod tests"))
3043 .expect("the source has a test module");
3044
3045 let mut functions: Vec<(String, String)> = Vec::new();
3046 for line in &lines[..test_module] {
3047 if let Some(name) = function_header_name(line) {
3048 functions.push((name.to_string(), String::new()));
3049 } else if let Some((_, body)) = functions.last_mut() {
3050 body.push_str(line);
3051 body.push('\n');
3052 }
3053 }
3054
3055 let mut checked = 0;
3056 let mut offenders = Vec::new();
3057 for (name, body) in functions {
3058 if !body.contains("StorageKeyType::KeyPackage.as_str()") {
3059 continue;
3060 }
3061 checked += 1;
3062 if body.contains(".delete(") {
3063 offenders.push(name);
3064 }
3065 }
3066 (checked, offenders)
3067 }
3068
3069 fn function_header_name(line: &str) -> Option<&str> {
3072 let trimmed = line.trim_start();
3073 if !matches!(line.len() - trimmed.len(), 0 | 4) {
3074 return None;
3075 }
3076 let mut rest = trimmed;
3077 loop {
3078 if let Some(after) = rest.strip_prefix("pub(") {
3079 rest = after.split_once(')')?.1.trim_start();
3080 } else if let Some(after) = ["pub ", "async ", "const ", "unsafe "]
3081 .iter()
3082 .find_map(|qualifier| rest.strip_prefix(qualifier))
3083 {
3084 rest = after.trim_start();
3085 } else {
3086 break;
3087 }
3088 }
3089 rest.strip_prefix("fn ")?.split(['(', '<']).next()
3090 }
3091
3092 #[test]
3096 fn test_push_path_claims_an_unclaimed_package_instead_of_stranding_it() {
3097 let manager = create_test_manager("alice");
3098 let legacy = manager.get_or_create_key_package().unwrap();
3099 assert!(legacy.assigned_peer.is_none());
3100
3101 let taken = manager.take_push_key_package(&addr("bob")).unwrap();
3102 assert_eq!(
3103 taken.bundle.package_id, legacy.package_id,
3104 "the pre-existing package was stranded instead of claimed"
3105 );
3106 assert_eq!(
3107 taken.bundle.assigned_peer.as_deref(),
3108 Some(addr("bob").as_str())
3109 );
3110 assert_eq!(manager.count_valid_key_packages().unwrap(), 1);
3111 }
3112
3113 #[test]
3118 fn test_push_pool_ceiling_shares_a_package_and_reports_it() {
3119 let manager = create_test_manager("alice");
3120
3121 for i in 0..MAX_PUSH_KEY_PACKAGES {
3122 let taken = manager.take_push_key_package(&format!("peer{i}")).unwrap();
3123 assert!(
3124 !taken.pool_exhausted,
3125 "pool reported exhausted at {i}, below the ceiling"
3126 );
3127 }
3128 assert_eq!(
3129 manager.count_valid_key_packages().unwrap(),
3130 MAX_PUSH_KEY_PACKAGES
3131 );
3132
3133 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
3134 assert!(over.pool_exhausted, "ceiling breach was not reported");
3135 assert_eq!(
3136 manager.count_valid_key_packages().unwrap(),
3137 MAX_PUSH_KEY_PACKAGES,
3138 "the pool grew past its ceiling"
3139 );
3140
3141 let established = manager.take_push_key_package("peer0").unwrap();
3144 assert!(!established.pool_exhausted);
3145 assert_eq!(established.bundle.assigned_peer.as_deref(), Some("peer0"));
3146 }
3147
3148 #[test]
3154 fn test_ceiling_claims_an_unclaimed_package_rather_than_sharing() {
3155 let manager = create_test_manager("alice");
3156
3157 for i in 0..(MAX_PUSH_KEY_PACKAGES - 1) {
3158 manager.take_push_key_package(&format!("peer{i}")).unwrap();
3159 }
3160 let unclaimed = manager.get_or_create_key_package().unwrap();
3163 assert_eq!(
3164 manager.count_valid_key_packages().unwrap(),
3165 MAX_PUSH_KEY_PACKAGES,
3166 "test premise: the pool is at the ceiling"
3167 );
3168
3169 let taken = manager.take_push_key_package("late-arrival").unwrap();
3170 assert!(
3171 !taken.pool_exhausted,
3172 "shared a package while a claimable one was in the pool"
3173 );
3174 assert_eq!(
3175 taken.bundle.package_id, unclaimed.package_id,
3176 "the claimable package was passed over"
3177 );
3178 assert_eq!(taken.bundle.assigned_peer.as_deref(), Some("late-arrival"));
3179 assert_eq!(
3180 manager.count_valid_key_packages().unwrap(),
3181 MAX_PUSH_KEY_PACKAGES,
3182 "claiming grew the pool"
3183 );
3184
3185 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
3188 assert!(over.pool_exhausted);
3189 }
3190
3191 #[test]
3192 fn test_import_key_package_binds_credential_identity() {
3193 let alice = create_test_manager("alice");
3194 let mallory = create_test_manager("mallory");
3195 let mallory_kp = mallory.generate_key_package().unwrap();
3196
3197 let err = alice
3200 .import_key_package(&addr("bob"), &mallory_kp.key_package_data)
3201 .unwrap_err();
3202 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3203
3204 alice
3206 .import_key_package(&addr("mallory"), &mallory_kp.key_package_data)
3207 .unwrap();
3208 }
3209
3210 fn substituted_key_package(claimed: &str, keys: &SignatureKeyPair) -> Vec<u8> {
3220 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3221 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
3222 keys.store(provider.storage()).unwrap();
3223
3224 let credential = CredentialWithKey {
3225 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
3226 signature_key: keys.public().into(),
3227 };
3228
3229 KeyPackage::builder()
3230 .build(DEFAULT_CIPHERSUITE, &provider, keys, credential)
3231 .unwrap()
3232 .key_package()
3233 .tls_serialize_detached()
3234 .unwrap()
3235 }
3236
3237 #[test]
3238 fn test_credential_identity_check_alone_does_not_stop_a_substitution() {
3239 let (mallorys_keys, _) = test_identity("mallory");
3244 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3245
3246 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3247 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
3248 let key_package = KeyPackageIn::tls_deserialize_exact(&forged)
3249 .unwrap()
3250 .validate(provider.crypto(), ProtocolVersion::Mls10)
3251 .unwrap();
3252
3253 MlsManager::verify_credential_identity(&key_package, &addr("bob"))
3254 .expect("the identity check cannot tell the two bobs apart");
3255 }
3256
3257 #[test]
3258 fn test_substituted_key_package_is_refused_by_the_derivation() {
3259 let alice = create_test_manager("alice");
3264 let real_bob = create_test_manager("bob");
3265 let (mallorys_keys, _) = test_identity("mallory");
3266
3267 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3268 let err = alice.import_key_package(&addr("bob"), &forged).unwrap_err();
3269 assert!(
3270 matches!(
3271 err,
3272 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
3273 ),
3274 "expected an address mismatch, got {:?}",
3275 err
3276 );
3277
3278 let real_kp = real_bob.generate_key_package().unwrap();
3280 alice
3281 .import_key_package(&addr("bob"), &real_kp.key_package_data)
3282 .expect("the real peer's key package derives to its own address");
3283 }
3284
3285 #[test]
3286 fn test_a_nickname_peer_id_is_refused_rather_than_skipping_the_check() {
3287 let alice = create_test_manager("alice");
3291 let bob_kp = create_test_manager("bob").generate_key_package().unwrap();
3292
3293 let err = alice
3294 .import_key_package("bob", &bob_kp.key_package_data)
3295 .unwrap_err();
3296 assert!(
3297 matches!(
3298 err,
3299 MlsError::CredentialIdentityMismatch { .. } | MlsError::InvalidUserId(_)
3300 ),
3301 "a nickname must be refused, got {:?}",
3302 err
3303 );
3304 }
3305
3306 #[test]
3307 fn test_add_group_member_binds_identity_and_derivation() {
3308 let alice = create_test_manager("alice");
3312 let bob = create_test_manager("bob");
3313 let mallory = create_test_manager("mallory");
3314
3315 let group_id = alice.create_group("Test Group").unwrap().group_id;
3316
3317 let mallory_kp = mallory.generate_key_package().unwrap();
3319 let err = alice
3320 .add_group_member(&group_id, &addr("bob"), &mallory_kp.key_package_data)
3321 .unwrap_err();
3322 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3323
3324 let (mallorys_keys, _) = test_identity("mallory");
3326 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3327 let err = alice
3328 .add_group_member(&group_id, &addr("bob"), &forged)
3329 .unwrap_err();
3330 assert!(matches!(err, MlsError::KeyPackageAddressMismatch { .. }));
3331
3332 let bob_kp = bob.generate_key_package().unwrap();
3334 alice
3335 .add_group_member(&group_id, &addr("bob"), &bob_kp.key_package_data)
3336 .expect("the real invitee is admitted");
3337 }
3338
3339 #[test]
3340 fn test_import_key_package_rejects_hostile_user_id() {
3341 let alice = create_test_manager("alice");
3342 let bob = create_test_manager("bob");
3343 let bob_kp = bob.generate_key_package().unwrap();
3344
3345 for hostile in ["", "..", "bob/evil", "bob\\evil", "bob\0evil"] {
3346 let err = alice
3347 .import_key_package(hostile, &bob_kp.key_package_data)
3348 .unwrap_err();
3349 assert!(
3350 matches!(err, MlsError::InvalidUserId(_)),
3351 "expected InvalidUserId for {:?}, got {:?}",
3352 hostile,
3353 err
3354 );
3355 }
3356 }
3357
3358 #[test]
3359 fn test_get_contact_key_package_rejects_poisoned_store() {
3360 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3364 let (alice_keys, alice_address) = test_identity("alice");
3365 seed_identity(&storage, &alice_keys);
3366 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
3367 let mallory = create_test_manager("mallory");
3368 let mallory_kp = mallory.generate_key_package().unwrap();
3369
3370 storage
3371 .store(
3372 StorageKeyType::ContactKeyPackage.as_str(),
3373 &addr("bob"),
3374 &mallory_kp.key_package_data,
3375 )
3376 .unwrap();
3377
3378 let err = alice.create_session(&addr("bob")).unwrap_err();
3380 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3381 }
3382
3383 #[test]
3402 fn test_get_contact_key_package_rejects_a_substituted_package() {
3403 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3404 let (alice_keys, alice_address) = test_identity("alice");
3405 seed_identity(&storage, &alice_keys);
3406 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
3407
3408 let (mallorys_keys, _) = test_identity("mallory");
3410 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3411
3412 storage
3413 .store(
3414 StorageKeyType::ContactKeyPackage.as_str(),
3415 &addr("bob"),
3416 &forged,
3417 )
3418 .unwrap();
3419
3420 let err = alice.create_session(&addr("bob")).unwrap_err();
3421 assert!(
3422 matches!(
3423 err,
3424 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
3425 ),
3426 "a substituted cached package must be refused at use time, got {:?}",
3427 err
3428 );
3429
3430 assert!(!alice.has_session(&addr("bob")).unwrap());
3432 }
3433
3434 #[test]
3435 fn test_replace_session_with_welcome_rejects_hostile_inviter_id() {
3436 let alice = create_test_manager("alice");
3440 let welcome = WelcomeMessage {
3441 group_id: slot("alice", "bob"),
3442 welcome_data: vec![],
3443 inviter_id: "../../etc".to_string(),
3444 group_name: None,
3445 timestamp_ms: 0,
3446 };
3447 let err = alice.replace_session_with_welcome(&welcome).unwrap_err();
3448 assert!(matches!(err, MlsError::InvalidUserId(_)));
3449 }
3450
3451 fn create_test_session() -> (MlsManager, MlsManager) {
3453 let alice = create_test_manager("alice");
3454 let bob = create_test_manager("bob");
3455 let bob_kp = bob.generate_key_package().unwrap();
3456 alice
3457 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3458 .unwrap();
3459 let welcome = alice.create_session(&addr("bob")).unwrap();
3460 bob.join_session(&welcome).unwrap();
3461 (alice, bob)
3462 }
3463
3464 use crate::storage::{StorageError, StorageResult};
3465
3466 struct StampDeleteFails(InMemoryStorage);
3468
3469 impl MlsStorage for StampDeleteFails {
3470 fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> StorageResult<()> {
3471 self.0.store(key_type, key_id, data)
3472 }
3473 fn load(&self, key_type: &str, key_id: &str) -> StorageResult<Option<Vec<u8>>> {
3474 self.0.load(key_type, key_id)
3475 }
3476 fn delete(&self, key_type: &str, key_id: &str) -> StorageResult<()> {
3477 if key_type == StorageKeyType::GroupMetadata.as_str() {
3478 return Err(StorageError::DeleteFailed("refused".to_string()));
3479 }
3480 self.0.delete(key_type, key_id)
3481 }
3482 fn list_keys(&self, key_type: &str) -> StorageResult<Vec<String>> {
3483 self.0.list_keys(key_type)
3484 }
3485 }
3486
3487 #[test]
3491 fn a_failed_delete_session_leaves_the_session_in_place() {
3492 let storage: Arc<dyn MlsStorage> = Arc::new(StampDeleteFails(InMemoryStorage::new()));
3493 let (keys, address) = test_identity("alice");
3494 seed_identity(&storage, &keys);
3495 let alice = MlsManager::new(address.to_string(), storage).unwrap();
3496 let bob = create_test_manager("bob");
3497 let bob_kp = bob.generate_key_package().unwrap();
3498 alice
3499 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3500 .unwrap();
3501 alice.create_session(&addr("bob")).unwrap();
3502
3503 assert!(alice.delete_session(&addr("bob")).is_err());
3504 assert!(alice.has_session(&addr("bob")).unwrap());
3505 assert!(alice
3506 .session_established_at_ms(&addr("bob"))
3507 .unwrap()
3508 .is_some());
3509 }
3510
3511 #[test]
3512 fn test_session_decrypt_rejects_spoofed_sender() {
3513 let (alice, bob) = create_test_session();
3514
3515 let ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
3521 let err = bob.decrypt_from_user(&ct, &addr("mallory")).unwrap_err();
3522 assert!(
3523 matches!(err, MlsError::SessionIdentityMismatch { .. }),
3524 "spoofed sender must be rejected by the slot binding, got {:?}",
3525 err
3526 );
3527
3528 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
3533 assert_eq!(pt.as_deref(), Some(&b"hello"[..]));
3534
3535 }
3539
3540 #[test]
3541 fn test_epoch_fork_classifies_as_session_desync() {
3542 let (alice, bob) = create_test_session();
3547
3548 let group_id = slot("alice", "bob");
3551 alice.update_keys(&group_id).unwrap();
3552
3553 let ct = alice.encrypt_for_user(&addr("bob"), b"after-fork").unwrap();
3556 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
3557 assert!(
3558 matches!(err, MlsError::SessionDesync(_)),
3559 "epoch fork must classify as SessionDesync, got {:?}",
3560 err
3561 );
3562 }
3563
3564 fn push_varint(out: &mut Vec<u8>, len: usize) {
3577 match len {
3578 0..=63 => out.push(len as u8),
3579 64..=16_383 => {
3580 out.push(0x40 | (len >> 8) as u8);
3581 out.push((len & 0xff) as u8);
3582 }
3583 _ => panic!("test payloads stay under the 2-byte varint ceiling"),
3584 }
3585 }
3586
3587 fn forge_private_message(group_id: &str, epoch: u64) -> Vec<u8> {
3588 let mut out = Vec::new();
3589 out.extend_from_slice(&1u16.to_be_bytes()); out.extend_from_slice(&2u16.to_be_bytes()); push_varint(&mut out, group_id.len());
3592 out.extend_from_slice(group_id.as_bytes());
3593 out.extend_from_slice(&epoch.to_be_bytes());
3594 out.push(1); out.push(0); out.push(8); out.extend_from_slice(&[0u8; 8]);
3598 out.push(16); out.extend_from_slice(&[0u8; 16]);
3600 out
3601 }
3602
3603 #[test]
3604 fn test_forged_frame_reaches_session_desync_without_any_key_material() {
3605 let (_alice, bob) = create_test_session();
3618
3619 let forged = EncryptedMessage {
3620 group_id: slot("alice", "bob"),
3621 message_type: MlsMessageType::Application,
3622 epoch: 9_999,
3623 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
3624 sender_id: addr("alice"),
3625 timestamp_ms: 0,
3626 };
3627
3628 let err = bob.decrypt_from_user(&forged, &addr("alice")).unwrap_err();
3629 assert!(
3630 matches!(err, MlsError::SessionDesync(_)),
3631 "a forged future-epoch frame reaches the desync classification pre-AEAD, got {:?}",
3632 err
3633 );
3634 }
3635
3636 #[test]
3637 fn test_forged_frame_naming_a_foreign_slot_is_rejected_before_desync() {
3638 let (_alice, bob) = create_test_session();
3644
3645 let forged = EncryptedMessage {
3646 group_id: slot("alice", "bob"),
3647 message_type: MlsMessageType::Application,
3648 epoch: 9_999,
3649 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
3650 sender_id: addr("mallory"),
3651 timestamp_ms: 0,
3652 };
3653
3654 let err = bob
3655 .decrypt_from_user(&forged, &addr("mallory"))
3656 .unwrap_err();
3657 assert!(
3658 matches!(err, MlsError::SessionIdentityMismatch { .. }),
3659 "a frame naming another pair's slot must not classify as recoverable, got {:?}",
3660 err
3661 );
3662 }
3663
3664 #[test]
3665 fn test_corrupt_ciphertext_is_not_classified_as_session_desync() {
3666 let (alice, bob) = create_test_session();
3672 let mut ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
3673 for b in ct.ciphertext.iter_mut() {
3675 *b ^= 0xFF;
3676 }
3677 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
3678 assert!(
3679 !matches!(err, MlsError::SessionDesync(_)),
3680 "corrupt ciphertext must not be treated as a recoverable desync, got {:?}",
3681 err
3682 );
3683 }
3684
3685 #[test]
3686 fn test_join_session_binds_slot_to_inviter() {
3687 let bob = create_test_manager("bob");
3694
3695 let hijack = WelcomeMessage {
3698 group_id: slot("alice", "bob"),
3699 welcome_data: vec![], inviter_id: addr("mallory"),
3701 group_name: None,
3702 timestamp_ms: 0,
3703 };
3704
3705 let err = bob.join_session(&hijack).unwrap_err();
3707 assert!(
3708 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3709 "join_session: expected WelcomeIdentityMismatch, got {:?}",
3710 err
3711 );
3712 let err = bob.replace_session_with_welcome(&hijack).unwrap_err();
3713 assert!(
3714 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3715 "replace_session_with_welcome: expected WelcomeIdentityMismatch, got {:?}",
3716 err
3717 );
3718
3719 assert!(!bob.has_session(&addr("alice")).unwrap());
3721 assert!(!bob.has_session(&addr("mallory")).unwrap());
3722
3723 let bob_kp = bob.generate_key_package().unwrap();
3727 let mallory = create_test_manager("mallory");
3728 mallory
3729 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3730 .unwrap();
3731 let legit = mallory.create_session(&addr("bob")).unwrap();
3732 bob.join_session(&legit).unwrap();
3733 assert!(bob.has_session(&addr("mallory")).unwrap());
3734 }
3735
3736 #[test]
3737 fn test_join_group_rejects_session_namespace() {
3738 let bob = create_test_manager("bob");
3746
3747 let squat = WelcomeMessage {
3748 group_id: slot("alice", "bob"),
3749 welcome_data: vec![], inviter_id: addr("mallory"),
3751 group_name: None,
3752 timestamp_ms: 0,
3753 };
3754
3755 let err = bob.join_group(&squat).unwrap_err();
3756 assert!(
3757 matches!(err, MlsError::ReservedSessionNamespace { .. }),
3758 "join_group: expected ReservedSessionNamespace, got {:?}",
3759 err
3760 );
3761
3762 assert!(!bob.has_session(&addr("alice")).unwrap());
3764
3765 let legit_ns = WelcomeMessage {
3769 group_id: GroupId::new("group:0bd6e5f2-3a70-4a4e-9c3f-1c1f2a3b4c5d").unwrap(),
3770 welcome_data: vec![],
3771 inviter_id: addr("mallory"),
3772 group_name: None,
3773 timestamp_ms: 0,
3774 };
3775 let err = bob.join_group(&legit_ns).unwrap_err();
3776 assert!(
3777 !matches!(err, MlsError::ReservedSessionNamespace { .. }),
3778 "a group:-namespaced Welcome must not trip the session-namespace guard, got {:?}",
3779 err
3780 );
3781 }
3782
3783 #[test]
3784 fn test_join_group_rejects_embedded_group_id_mismatch() {
3785 let bob = create_test_manager("bob");
3794 let mallory = create_test_manager("mallory");
3795
3796 let bob_kp = bob.generate_key_package().unwrap();
3798 let embedded = GroupId::new("group:11111111-1111-4111-8111-111111111111").unwrap();
3799 let cred = mallory.get_credential().unwrap();
3800 let signer = mallory.get_signer().unwrap();
3801 mallory
3802 .group_manager
3803 .create_group(&embedded, &cred, &signer)
3804 .unwrap();
3805 let (welcome, _commit) = mallory
3806 .add_group_member(&embedded, &addr("bob"), &bob_kp.key_package_data)
3807 .unwrap();
3808 assert_eq!(welcome.group_id, embedded);
3809
3810 let wire = GroupId::new("group:22222222-2222-4222-8222-222222222222").unwrap();
3814 let tampered = WelcomeMessage {
3815 group_id: wire.clone(),
3816 ..welcome
3817 };
3818
3819 let err = bob.join_group(&tampered).unwrap_err();
3820 assert!(
3821 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3822 "expected WelcomeGroupIdMismatch, got {:?}",
3823 err
3824 );
3825
3826 assert!(bob.group_manager.load_group(&wire).unwrap().is_none());
3828 assert!(bob.group_manager.load_group(&embedded).unwrap().is_none());
3829 }
3830
3831 #[test]
3832 fn test_welcome_embedded_id_cannot_hijack_session_slot() {
3833 let alice = create_test_manager("alice");
3842 let bob = create_test_manager("bob");
3843 let bob_kp = bob.generate_key_package().unwrap();
3844 alice
3845 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3846 .unwrap();
3847 let welcome = alice.create_session(&addr("bob")).unwrap();
3848 bob.join_session(&welcome).unwrap();
3849 assert!(bob.has_session(&addr("alice")).unwrap());
3850
3851 let mallory = create_test_manager("mallory");
3853 let bob_kp2 = bob.generate_key_package().unwrap();
3854 let squat = slot("alice", "bob");
3855 let cred = mallory.get_credential().unwrap();
3856 let signer = mallory.get_signer().unwrap();
3857 mallory
3858 .group_manager
3859 .create_group(&squat, &cred, &signer)
3860 .unwrap();
3861 let (mal_welcome, _commit) = mallory
3862 .add_group_member(&squat, &addr("bob"), &bob_kp2.key_package_data)
3863 .unwrap();
3864
3865 let attack = WelcomeMessage {
3867 group_id: slot("bob", "mallory"),
3868 welcome_data: mal_welcome.welcome_data,
3869 inviter_id: addr("mallory"),
3870 group_name: None,
3871 timestamp_ms: 0,
3872 };
3873
3874 let err = bob.join_session(&attack).unwrap_err();
3875 assert!(
3876 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3877 "expected WelcomeGroupIdMismatch, got {:?}",
3878 err
3879 );
3880
3881 assert!(bob.has_session(&addr("alice")).unwrap());
3883 let ct = bob
3884 .encrypt_for_user(&addr("alice"), b"still private")
3885 .unwrap();
3886 let pt = alice.decrypt_from_user(&ct, &addr("bob")).unwrap();
3887 assert_eq!(pt.as_deref(), Some(&b"still private"[..]));
3888 }
3889
3890 fn create_test_group_with_bob() -> (MlsManager, MlsManager, GroupId) {
3893 let alice = create_test_manager("alice");
3894 let bob = create_test_manager("bob");
3895 let bob_kp = bob.generate_key_package().unwrap();
3896 alice
3897 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3898 .unwrap();
3899 let info = alice.create_group("Test Group").unwrap();
3900 let gid = info.group_id.clone();
3901 let (welcome, _commit) = alice
3902 .add_group_member(&gid, &addr("bob"), &bob_kp.key_package_data)
3903 .unwrap();
3904 bob.join_group(&welcome).unwrap();
3905 (alice, bob, gid)
3906 }
3907
3908 #[test]
3909 fn test_group_decrypt_rejects_spoofed_sender() {
3910 let (alice, bob, gid) = create_test_group_with_bob();
3911
3912 let ct = alice.encrypt_for_group(&gid, b"group message").unwrap();
3913 let err = bob.decrypt_from_group(&ct, &addr("mallory")).unwrap_err();
3914 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3915
3916 let ct2 = alice.encrypt_for_group(&gid, b"another one").unwrap();
3917 let pt = bob.decrypt_from_group(&ct2, &addr("alice")).unwrap();
3918 assert_eq!(pt.as_deref(), Some(&b"another one"[..]));
3919 }
3920
3921 #[test]
3922 fn test_group_commit_with_spoofed_sender_rejected_before_merge() {
3923 let (alice, bob, gid) = create_test_group_with_bob();
3924
3925 let commit = alice.update_keys(&gid).unwrap();
3929 let epoch_before = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3930
3931 let err = bob
3932 .decrypt_from_group(&commit, &addr("mallory"))
3933 .unwrap_err();
3934 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3935
3936 let epoch_after = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3937 assert_eq!(
3938 epoch_before, epoch_after,
3939 "spoofed commit must not advance group state"
3940 );
3941 }
3942
3943 #[test]
3944 fn test_no_session_initially() {
3945 let manager = create_test_manager("alice");
3946 assert!(!manager.has_session(&addr("bob")).unwrap());
3947 assert!(manager.list_sessions().unwrap().is_empty());
3948 }
3949
3950 #[test]
3951 fn test_no_groups_initially() {
3952 let manager = create_test_manager("alice");
3953 assert!(manager.list_groups().unwrap().is_empty());
3954 }
3955
3956 #[test]
3957 fn test_group_creation() {
3958 let manager = create_test_manager("alice");
3959 let info = manager.create_group("Test Group").unwrap();
3960
3961 assert_eq!(info.name, Some("Test Group".to_string()));
3962 assert!(!info.is_session);
3963 assert_eq!(info.members.len(), 1);
3964
3965 let groups = manager.list_groups().unwrap();
3966 assert_eq!(groups.len(), 1);
3967 }
3968
3969 #[test]
3980 fn test_both_create_adopt_is_non_destructive_on_welcome_retransmit() {
3981 let alice = create_test_manager("alice");
3983 let bob = create_test_manager("bob");
3984
3985 let alice_kp = alice.generate_key_package().unwrap();
3987 let bob_kp = bob.generate_key_package().unwrap();
3988 alice
3989 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3990 .unwrap();
3991 bob.import_key_package(&addr("alice"), &alice_kp.key_package_data)
3992 .unwrap();
3993
3994 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());
3998
3999 bob.join_session(&alice_welcome).unwrap();
4002 assert!(bob.has_session(&addr("alice")).unwrap());
4003
4004 let ct = alice
4006 .encrypt_for_user(&addr("bob"), b"hello over the converged group")
4007 .unwrap();
4008 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
4009 assert_eq!(pt.as_deref(), Some(&b"hello over the converged group"[..]));
4010
4011 let retransmit = bob.join_session(&alice_welcome);
4014 assert!(
4015 retransmit.is_err(),
4016 "re-staging a consumed key package should fail"
4017 );
4018
4019 assert!(
4022 bob.has_session(&addr("alice")).unwrap(),
4023 "duplicate Welcome must not brick the converged session"
4024 );
4025
4026 let ct2 = alice
4028 .encrypt_for_user(&addr("bob"), b"still converged after retransmit")
4029 .unwrap();
4030 let pt2 = bob.decrypt_from_user(&ct2, &addr("alice")).unwrap();
4031 assert_eq!(
4032 pt2.as_deref(),
4033 Some(&b"still converged after retransmit"[..])
4034 );
4035 }
4036
4037 #[test]
4043 fn test_out_of_order_decryption_within_sender_ratchet_tolerance() {
4044 let alice = create_test_manager("alice");
4045 let bob = create_test_manager("bob");
4046
4047 let bob_kp = bob.generate_key_package().unwrap();
4048 alice
4049 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4050 .unwrap();
4051 let welcome = alice.create_session(&addr("bob")).unwrap();
4052 bob.join_session(&welcome).unwrap();
4053
4054 let ciphertexts: Vec<_> = (0..40)
4055 .map(|i| {
4056 alice
4057 .encrypt_for_user(&addr("bob"), format!("chunk {}", i).as_bytes())
4058 .unwrap()
4059 })
4060 .collect();
4061
4062 let pt = bob
4065 .decrypt_from_user(&ciphertexts[39], &addr("alice"))
4066 .unwrap();
4067 assert_eq!(pt.as_deref(), Some(&b"chunk 39"[..]));
4068
4069 let pt = bob
4072 .decrypt_from_user(&ciphertexts[10], &addr("alice"))
4073 .unwrap();
4074 assert_eq!(pt.as_deref(), Some(&b"chunk 10"[..]));
4075
4076 let res = bob.decrypt_from_user(&ciphertexts[0], &addr("alice"));
4079 assert!(
4080 !matches!(res, Ok(Some(_))),
4081 "generation beyond the tolerance must not decrypt"
4082 );
4083 }
4084
4085 const RFC8032_TV1_PK: [u8; 32] = [
4087 0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07,
4088 0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07,
4089 0x51, 0x1a,
4090 ];
4091
4092 const RFC8032_TV1_ADDRESS: &str = "off1qysluvwl5922yctzd0u9gpr06gn3k7ldfvgtwgvn";
4097
4098 #[test]
4106 fn derive_address_matches_the_pinned_vector() {
4107 let address = MlsManager::derive_address(&RFC8032_TV1_PK).expect("32-byte key derives");
4108 assert_eq!(address.to_string(), RFC8032_TV1_ADDRESS);
4109 assert_eq!(
4112 RFC8032_TV1_ADDRESS.parse::<Address>().expect("parses"),
4113 address
4114 );
4115 }
4116
4117 #[test]
4118 fn derive_address_rejects_wrong_key_lengths() {
4119 for len in [0usize, 31, 33, 64] {
4120 let key = vec![0u8; len];
4121 assert!(
4122 matches!(
4123 MlsManager::derive_address(&key),
4124 Err(MlsError::InvalidPublicKey(_))
4125 ),
4126 "{len}-byte key must be rejected"
4127 );
4128 }
4129 }
4130
4131 #[test]
4132 fn deprecated_derive_agrees_with_derive_address() {
4133 #[allow(deprecated)]
4136 let legacy = MlsManager::derive_user_id_from_public_key(&RFC8032_TV1_PK);
4137 assert_eq!(legacy, RFC8032_TV1_ADDRESS);
4138 assert_eq!(
4139 legacy,
4140 MlsManager::derive_address(&RFC8032_TV1_PK)
4141 .expect("derives")
4142 .to_string()
4143 );
4144 }
4145
4146 #[test]
4147 fn load_or_create_identity_mints_once_and_is_idempotent() {
4148 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4149
4150 let (first_keys, first_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4151 let (second_keys, second_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4152
4153 assert_eq!(first_keys.public(), second_keys.public());
4155 assert_eq!(first_address, second_address);
4156 assert_eq!(
4157 MlsManager::derive_address(first_keys.public()).unwrap(),
4158 first_address,
4159 "the returned address must be the stored key's derivation"
4160 );
4161 }
4162
4163 #[test]
4164 fn load_or_create_identity_adopts_an_existing_key() {
4165 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4166 let (keys, address) = test_identity("alice");
4167 seed_identity(&storage, &keys);
4168
4169 let (loaded, loaded_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4170
4171 assert_eq!(loaded.public(), keys.public());
4172 assert_eq!(loaded_address, address);
4173 }
4174
4175 #[test]
4176 fn manager_bootstrapped_at_its_derived_address_loads_that_identity() {
4177 let (manager, address) = create_addressed_manager("alice");
4178
4179 assert_eq!(manager.user_id(), address.to_string());
4180 assert_eq!(
4181 MlsManager::derive_address(&manager.get_identity_public_key().unwrap()).unwrap(),
4182 address,
4183 "the manager's identity key must derive to the address it claims"
4184 );
4185 }
4186
4187 #[test]
4192 fn manager_refuses_an_address_whose_key_is_not_the_stored_one() {
4193 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4194 let (alice_keys, alice_address) = test_identity("alice");
4195 let (_, bob_address) = test_identity("bob");
4196 seed_identity(&storage, &alice_keys);
4197
4198 let Err(err) = MlsManager::new(bob_address.to_string(), storage) else {
4199 panic!("a manager must not claim an address it holds no key for");
4200 };
4201
4202 match err {
4203 MlsError::IdentityAddressMismatch { expected, derived } => {
4204 assert_eq!(expected, bob_address.to_string());
4205 assert_eq!(derived, alice_address.to_string());
4206 }
4207 other => panic!("expected IdentityAddressMismatch, got {other:?}"),
4208 }
4209 }
4210
4211 #[test]
4215 fn manager_refuses_to_claim_an_address_with_no_stored_identity() {
4216 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4217 let (_, address) = test_identity("alice");
4218
4219 let Err(err) = MlsManager::new(address.to_string(), storage.clone()) else {
4220 panic!("a manager must not mint a key and claim a different address");
4221 };
4222 assert!(matches!(err, MlsError::IdentityAddressMismatch { .. }));
4223
4224 assert!(
4225 storage
4226 .load(StorageKeyType::Identity.as_str(), "key_pair")
4227 .unwrap()
4228 .is_none(),
4229 "a refused bootstrap must not leave a key behind"
4230 );
4231 }
4232
4233 #[test]
4245 fn manager_with_a_non_address_id_skips_the_binding_check() {
4246 let manager = MlsManager::new("alice", Arc::new(InMemoryStorage::new())).unwrap();
4247 assert_eq!(manager.user_id(), "alice");
4248 }
4249
4250 struct Impostor {
4263 provider: MlsProvider,
4264 keys: SignatureKeyPair,
4265 key_package: Vec<u8>,
4266 group: Option<MlsGroup>,
4267 }
4268
4269 impl Impostor {
4270 fn claiming(claimed: &str, key_label: &str) -> Self {
4273 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4274 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
4275 let (keys, _) = test_identity(key_label);
4276 keys.store(provider.storage()).unwrap();
4277
4278 let credential = CredentialWithKey {
4279 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
4280 signature_key: keys.public().into(),
4281 };
4282 let key_package = KeyPackage::builder()
4283 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
4284 .unwrap()
4285 .key_package()
4286 .tls_serialize_detached()
4287 .unwrap();
4288
4289 Self {
4290 provider,
4291 keys,
4292 key_package,
4293 group: None,
4294 }
4295 }
4296
4297 fn join(&mut self, welcome: &MlsMessageOut) {
4299 let bytes = welcome.tls_serialize_detached().unwrap();
4300 let welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
4301 .unwrap()
4302 .extract()
4303 {
4304 MlsMessageBodyIn::Welcome(w) => w,
4305 _ => panic!("not a welcome"),
4306 };
4307 let cfg = MlsGroupJoinConfig::builder()
4308 .use_ratchet_tree_extension(true)
4309 .build();
4310 self.group = Some(
4311 StagedWelcome::new_from_welcome(&self.provider, &cfg, welcome, None)
4312 .unwrap()
4313 .into_group(&self.provider)
4314 .unwrap(),
4315 );
4316 }
4317
4318 fn merge_commit(&mut self, commit_bytes: &[u8]) {
4324 let group = self.group.as_mut().expect("join first");
4325 let message = MlsMessageIn::tls_deserialize_exact(commit_bytes)
4326 .unwrap()
4327 .try_into_protocol_message()
4328 .unwrap();
4329 let processed = group.process_message(&self.provider, message).unwrap();
4330 match processed.into_content() {
4331 ProcessedMessageContent::StagedCommitMessage(staged) => {
4332 group.merge_staged_commit(&self.provider, *staged).unwrap();
4333 }
4334 _ => panic!("not a commit"),
4335 }
4336 }
4337
4338 fn encrypt(&mut self, plaintext: &[u8]) -> Vec<u8> {
4340 self.group
4341 .as_mut()
4342 .expect("join first")
4343 .create_message(&self.provider, &self.keys, plaintext)
4344 .unwrap()
4345 .tls_serialize_detached()
4346 .unwrap()
4347 }
4348 }
4349
4350 fn add_member_bypassing_checks(
4358 manager: &MlsManager,
4359 group_id: &GroupId,
4360 key_package_bytes: &[u8],
4361 ) -> (MlsMessageOut, MlsMessageOut) {
4362 let mut group = manager
4363 .group_manager
4364 .load_group(group_id)
4365 .unwrap()
4366 .expect("group exists");
4367 let key_package = KeyPackageIn::tls_deserialize_exact(key_package_bytes)
4368 .unwrap()
4369 .validate(
4370 manager.group_manager.provider().crypto(),
4371 ProtocolVersion::Mls10,
4372 )
4373 .unwrap();
4374 let signer = manager.get_signer().unwrap();
4375 let (commit, welcome) = manager
4376 .group_manager
4377 .add_member(&mut group, key_package, &signer)
4378 .unwrap();
4379 manager.group_manager.save_group(group_id, &group).unwrap();
4380 (commit, welcome)
4381 }
4382
4383 fn commit_message(
4384 group_id: &GroupId,
4385 commit: &MlsMessageOut,
4386 sender: &str,
4387 ) -> EncryptedMessage {
4388 EncryptedMessage {
4389 group_id: group_id.clone(),
4390 message_type: MlsMessageType::Commit,
4391 epoch: 0,
4392 ciphertext: commit.tls_serialize_detached().unwrap(),
4393 sender_id: sender.to_string(),
4394 timestamp_ms: 0,
4395 }
4396 }
4397
4398 fn app_message(group_id: &GroupId, ciphertext: Vec<u8>, sender: &str) -> EncryptedMessage {
4399 EncryptedMessage {
4400 group_id: group_id.clone(),
4401 message_type: MlsMessageType::Application,
4402 epoch: 0,
4403 ciphertext,
4404 sender_id: sender.to_string(),
4405 timestamp_ms: 0,
4406 }
4407 }
4408
4409 #[test]
4417 fn test_committed_forged_leaf_is_refused_and_cannot_speak_as_its_victim() {
4418 let mallory = create_test_manager("mallory");
4419 let alice = create_test_manager("alice");
4420 let alice_kp = alice.generate_key_package().unwrap();
4421
4422 let gid = mallory.create_group("Team").unwrap().group_id;
4423 let (welcome, _) = mallory
4424 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4425 .unwrap();
4426 alice.join_group(&welcome).unwrap();
4427 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
4428
4429 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4431 assert!(matches!(
4432 mallory
4433 .add_group_member(&gid, &addr("bob"), &impostor.key_package)
4434 .unwrap_err(),
4435 MlsError::KeyPackageAddressMismatch { .. }
4436 ));
4437 let (commit, forged_welcome) =
4438 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4439
4440 let err = alice
4442 .decrypt_from_group(
4443 &commit_message(&gid, &commit, &addr("mallory")),
4444 &addr("mallory"),
4445 )
4446 .unwrap_err();
4447 assert!(
4448 matches!(
4449 err,
4450 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4451 if *claimed == addr("bob") && *context == crate::error::LeafSource::CommitAdd
4452 ),
4453 "expected a CommitAdd binding refusal, got {err:?}"
4454 );
4455
4456 let after = alice.get_group_info(&gid).unwrap().unwrap();
4458 assert_eq!(
4459 after.epoch, epoch_before,
4460 "a refused commit advanced the epoch"
4461 );
4462 assert!(
4463 !after.members.contains(&addr("bob")),
4464 "roster: {:?}",
4465 after.members
4466 );
4467
4468 impostor.join(&forged_welcome);
4476 let forged = impostor.encrypt(b"send me the recovery phrase");
4477 assert!(
4478 alice
4479 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
4480 .is_err(),
4481 "a forged leaf's message was delivered"
4482 );
4483 }
4484
4485 #[test]
4489 fn test_welcome_carrying_a_forged_leaf_is_refused() {
4490 let mallory = create_test_manager("mallory");
4491 let alice = create_test_manager("alice");
4492
4493 let gid = mallory.create_group("Team").unwrap().group_id;
4494 let impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4495 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4496
4497 let alice_kp = alice.generate_key_package().unwrap();
4499 let (welcome, _) = mallory
4500 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4501 .unwrap();
4502
4503 let err = alice.join_group(&welcome).unwrap_err();
4504 assert!(
4505 matches!(
4506 err,
4507 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4508 if *claimed == addr("bob") && *context == crate::error::LeafSource::WelcomeTree
4509 ),
4510 "expected a WelcomeTree binding refusal, got {err:?}"
4511 );
4512
4513 assert!(alice.get_group_info(&gid).unwrap().is_none());
4515 }
4516
4517 #[test]
4522 fn test_session_welcome_carrying_a_forged_leaf_is_refused_non_destructively() {
4523 let alice = create_test_manager("alice");
4524 let bob = create_test_manager("bob");
4525 let mallory = create_test_manager("mallory");
4526
4527 let bob_kp = bob.generate_key_package().unwrap();
4529 alice
4530 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4531 .unwrap();
4532 bob.join_session(&alice.create_session(&addr("bob")).unwrap())
4533 .unwrap();
4534 assert!(bob.has_session(&addr("alice")).unwrap());
4535
4536 let bob_kp2 = bob.generate_key_package().unwrap();
4539 let slot_id = slot("bob", "mallory");
4540 let cred = mallory.get_credential().unwrap();
4541 let signer = mallory.get_signer().unwrap();
4542 mallory
4543 .group_manager
4544 .create_group(&slot_id, &cred, &signer)
4545 .unwrap();
4546 let impostor = Impostor::claiming(&addr("carol"), "mallorys-second-key");
4547 add_member_bypassing_checks(&mallory, &slot_id, &impostor.key_package);
4548 let (welcome, _) = mallory
4549 .add_group_member(&slot_id, &addr("bob"), &bob_kp2.key_package_data)
4550 .unwrap();
4551
4552 let err = bob.join_session(&welcome).unwrap_err();
4553 assert!(
4554 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
4555 if *context == crate::error::LeafSource::WelcomeTree),
4556 "expected a WelcomeTree binding refusal, got {err:?}"
4557 );
4558
4559 assert!(bob.has_session(&addr("alice")).unwrap());
4561 let ct = bob
4562 .encrypt_for_user(&addr("alice"), b"still private")
4563 .unwrap();
4564 assert_eq!(
4565 alice
4566 .decrypt_from_user(&ct, &addr("bob"))
4567 .unwrap()
4568 .as_deref(),
4569 Some(&b"still private"[..])
4570 );
4571 }
4572
4573 #[test]
4581 fn test_forged_leaf_seated_behind_the_gates_still_cannot_speak() {
4582 let mallory = create_test_manager("mallory");
4583 let alice = create_test_manager("alice");
4584
4585 let gid = mallory.create_group("Team").unwrap().group_id;
4586 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4587 let (_, forged_welcome) =
4588 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4589 impostor.join(&forged_welcome);
4590
4591 let alice_kp = alice.generate_key_package().unwrap();
4592 let (welcome, add_alice_commit) = mallory
4593 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4594 .unwrap();
4595 impostor.merge_commit(&add_alice_commit.ciphertext);
4598
4599 let bytes = welcome.welcome_data.clone();
4602 let staged_welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
4603 .unwrap()
4604 .extract()
4605 {
4606 MlsMessageBodyIn::Welcome(w) => w,
4607 _ => panic!("not a welcome"),
4608 };
4609 let cfg = MlsGroupJoinConfig::builder()
4610 .use_ratchet_tree_extension(true)
4611 .build();
4612 let group = StagedWelcome::new_from_welcome(&alice.provider, &cfg, staged_welcome, None)
4613 .unwrap()
4614 .into_group(&alice.provider)
4615 .unwrap();
4616 alice.group_manager.save_group(&gid, &group).unwrap();
4617
4618 assert!(
4620 group
4621 .members()
4622 .any(|m| m.credential.serialized_content() == addr("bob").as_bytes()),
4623 "test premise: the forged leaf must be installed"
4624 );
4625
4626 let forged = impostor.encrypt(b"send me the recovery phrase");
4628 let err = alice
4629 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
4630 .unwrap_err();
4631 assert!(
4632 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
4633 if *context == crate::error::LeafSource::MessageSender),
4634 "expected a MessageSender binding refusal, got {err:?}"
4635 );
4636
4637 let info = alice.get_group_info(&gid).unwrap().unwrap();
4640 assert!(
4641 !info.members.contains(&addr("bob")),
4642 "roster surfaced an unproven member: {:?}",
4643 info.members
4644 );
4645 assert!(
4646 info.members.contains(&addr("mallory")),
4647 "{:?}",
4648 info.members
4649 );
4650 }
4651
4652 #[test]
4666 fn test_member_renaming_their_own_leaf_to_a_peers_address_is_refused() {
4667 let (alice, bob, gid) = create_test_group_with_bob();
4671 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
4672
4673 let mut group = bob.group_manager.load_group(&gid).unwrap().unwrap();
4674 let signer = bob.get_signer().unwrap();
4675 let forged = CredentialWithKey {
4676 credential: Credential::new(CredentialType::Basic, addr("alice").as_bytes().to_vec()),
4679 signature_key: signer.public().into(),
4680 };
4681 let params = LeafNodeParameters::builder()
4682 .with_credential_with_key(forged)
4683 .build();
4684 let bundle = group
4685 .self_update(&bob.provider, &signer, params)
4686 .expect("MLS permits a credential change; the AS is what refuses it");
4687 group.merge_pending_commit(&bob.provider).unwrap();
4688 bob.group_manager.save_group(&gid, &group).unwrap();
4689 let (commit, _, _) = bundle.into_contents();
4690
4691 let err = alice
4692 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4693 .unwrap_err();
4694 assert!(
4695 matches!(
4696 err,
4697 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4698 if *claimed == addr("alice") && *context == crate::error::LeafSource::CommitPath
4699 ),
4700 "expected a CommitPath binding refusal, got {err:?}"
4701 );
4702
4703 let after = alice.get_group_info(&gid).unwrap().unwrap();
4706 assert_eq!(after.epoch, epoch_before);
4707 assert!(after.members.contains(&addr("bob")), "{:?}", after.members);
4708 assert_eq!(
4709 after
4710 .members
4711 .iter()
4712 .filter(|m| **m == addr("alice"))
4713 .count(),
4714 1,
4715 "alice must appear once, as herself: {:?}",
4716 after.members
4717 );
4718 }
4719
4720 #[test]
4723 fn test_leaf_with_a_nickname_credential_is_refused() {
4724 let mallory = create_test_manager("mallory");
4725 let alice = create_test_manager("alice");
4726 let alice_kp = alice.generate_key_package().unwrap();
4727
4728 let gid = mallory.create_group("Team").unwrap().group_id;
4729 let impostor = Impostor::claiming("bob", "mallorys-second-key");
4730 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4731 let (welcome, _) = mallory
4732 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4733 .unwrap();
4734
4735 let err = alice.join_group(&welcome).unwrap_err();
4736 assert!(
4737 matches!(err, MlsError::LeafAddressMismatch { ref claimed, .. } if claimed == "bob"),
4738 "a nickname credential was accepted or misreported: {err:?}"
4739 );
4740 }
4741
4742 #[test]
4750 fn test_key_rotation_and_ordinary_traffic_still_pass_the_binding() {
4751 let (alice, bob, gid) = create_test_group_with_bob();
4752
4753 let commit = alice.update_keys(&gid).unwrap();
4754 bob.decrypt_from_group(&commit, &addr("alice"))
4755 .expect("an honest key rotation must still merge");
4756
4757 let ct = alice.encrypt_for_group(&gid, b"after rotation").unwrap();
4758 assert_eq!(
4759 bob.decrypt_from_group(&ct, &addr("alice"))
4760 .unwrap()
4761 .as_deref(),
4762 Some(&b"after rotation"[..])
4763 );
4764
4765 assert!(bob
4767 .get_group_info(&gid)
4768 .unwrap()
4769 .unwrap()
4770 .members
4771 .contains(&addr("bob")));
4772 }
4773
4774 #[test]
4782 fn test_welcome_whose_group_context_has_external_senders_is_refused() {
4783 let mallory = create_test_manager("mallory");
4784 let alice = create_test_manager("alice");
4785
4786 let gid = mallory.create_group("Team").unwrap().group_id;
4787
4788 {
4790 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
4791 let signer = mallory.get_signer().unwrap();
4792 let outsider =
4793 SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
4794 let external = ExternalSender::new(
4795 outsider.public().into(),
4796 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
4797 );
4798 group
4799 .commit_builder()
4800 .propose_group_context_extensions(Extensions::single(Extension::ExternalSenders(
4801 vec![external],
4802 )))
4803 .load_psks(mallory.provider.storage())
4804 .unwrap()
4805 .build(
4806 mallory.provider.rand(),
4807 mallory.provider.crypto(),
4808 &signer,
4809 |_| true,
4810 )
4811 .unwrap()
4812 .stage_commit(&mallory.provider)
4813 .unwrap();
4814 group.merge_pending_commit(&mallory.provider).unwrap();
4815 mallory.group_manager.save_group(&gid, &group).unwrap();
4816 }
4817
4818 let alice_kp = alice.generate_key_package().unwrap();
4819 let (welcome, _) = mallory
4820 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4821 .unwrap();
4822
4823 let err = alice.join_group(&welcome).unwrap_err();
4824 assert!(
4825 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4826 if detail.contains("ExternalSenders")),
4827 "expected the invite to be declined for its ExternalSenders context, got {err:?}"
4828 );
4829 assert!(
4830 alice.get_group_info(&gid).unwrap().is_none(),
4831 "a declined Welcome must not install the group"
4832 );
4833 }
4834
4835 #[test]
4844 fn test_commit_proposing_external_senders_is_refused() {
4845 let (alice, mallory, gid) = create_test_group_with_bob();
4846
4847 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
4848 let signer = mallory.get_signer().unwrap();
4849
4850 let outsider = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
4852 let external = ExternalSender::new(
4853 outsider.public().into(),
4854 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
4855 );
4856 let extensions = Extensions::single(Extension::ExternalSenders(vec![external]));
4857
4858 let bundle = group
4859 .commit_builder()
4860 .propose_group_context_extensions(extensions)
4861 .load_psks(mallory.provider.storage())
4862 .unwrap()
4863 .build(
4864 mallory.provider.rand(),
4865 mallory.provider.crypto(),
4866 &signer,
4867 |_| true,
4868 )
4869 .unwrap()
4870 .stage_commit(&mallory.provider)
4871 .unwrap();
4872 let (commit, _, _) = bundle.into_contents();
4873
4874 let err = alice
4875 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4876 .unwrap_err();
4877 assert!(
4878 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4879 if detail.contains("ExternalSenders")),
4880 "expected an ExternalSenders refusal, got {err:?}"
4881 );
4882 }
4883
4884 #[test]
4894 fn test_one_identity_cannot_hold_two_leaves_in_a_group() {
4895 let alice = create_test_manager("alice");
4896 let bob = create_test_manager("bob");
4897
4898 let gid = alice.create_group("Team").unwrap().group_id;
4899 let first = bob.generate_key_package().unwrap();
4900 alice
4901 .add_group_member(&gid, &addr("bob"), &first.key_package_data)
4902 .unwrap();
4903
4904 let second = bob.generate_key_package().unwrap();
4907 assert_ne!(first.package_id, second.package_id);
4908 let err = alice
4909 .add_group_member(&gid, &addr("bob"), &second.key_package_data)
4910 .unwrap_err();
4911 assert!(
4912 matches!(err, MlsError::AddMember(ref m) if m.contains("Duplicate signature key")),
4913 "expected MLS to refuse a duplicate signature key, got {err:?}"
4914 );
4915
4916 alice.remove_group_member(&gid, &addr("bob")).unwrap();
4918 assert!(!alice
4919 .get_group_info(&gid)
4920 .unwrap()
4921 .unwrap()
4922 .members
4923 .contains(&addr("bob")));
4924 }
4925
4926 #[test]
4942 fn test_external_join_proposal_is_refused_as_an_unsupported_sender() {
4943 let (alice, _bob, gid) = create_test_group_with_bob();
4944
4945 let outsider = create_test_manager("mallory");
4949 let kp_bytes = outsider.generate_key_package().unwrap().key_package_data;
4950 let key_package = KeyPackageIn::tls_deserialize_exact(&kp_bytes)
4951 .unwrap()
4952 .validate(alice.provider.crypto(), ProtocolVersion::Mls10)
4953 .unwrap();
4954 let signer = outsider.get_signer().unwrap();
4955
4956 let (mls_group_id, epoch) = {
4957 let group = alice.group_manager.load_group(&gid).unwrap().unwrap();
4958 (group.group_id().clone(), group.epoch())
4959 };
4960 let proposal =
4961 JoinProposal::new::<MlsStorageAdapter>(key_package, mls_group_id, epoch, &signer)
4962 .expect(
4963 "an external join proposal is well-formed; the SDK just declines to honour it",
4964 );
4965
4966 let err = alice
4967 .decrypt_from_group(
4968 &app_message(
4969 &gid,
4970 proposal.tls_serialize_detached().unwrap(),
4971 &addr("mallory"),
4972 ),
4973 &addr("mallory"),
4974 )
4975 .unwrap_err();
4976 assert!(
4977 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4978 if detail.contains("not a group member")),
4979 "expected an UnsupportedSender refusal, got {err:?}"
4980 );
4981 }
4982
4983 #[test]
4997 fn test_roster_read_counts_the_leaves_it_skips() {
4998 let (alice, _bob, gid) = create_test_group_with_bob();
4999
5000 let before = alice.get_group_info(&gid).unwrap().unwrap();
5001 assert_eq!(
5002 before.unproven_members, 0,
5003 "an honest group must report no unproven members: {:?}",
5004 before.members
5005 );
5006 let honest_members = before.members.len();
5007
5008 let impostor = Impostor::claiming(&addr("carol"), "an-attackers-key");
5011 add_member_bypassing_checks(&alice, &gid, &impostor.key_package);
5012
5013 let after = alice.get_group_info(&gid).unwrap().unwrap();
5014 assert_eq!(
5015 after.unproven_members, 1,
5016 "the skipped leaf must be counted, not only dropped"
5017 );
5018 assert_eq!(
5019 after.members.len(),
5020 honest_members,
5021 "the roster must be unchanged: {:?}",
5022 after.members
5023 );
5024 assert!(
5025 !after.members.contains(&addr("carol")),
5026 "roster surfaced the unproven member: {:?}",
5027 after.members
5028 );
5029 }
5030
5031 fn key_package_with_window(label: &str, lifetime: Lifetime) -> (String, Vec<u8>) {
5041 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
5042 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
5043 let (keys, address) = test_identity(label);
5044 keys.store(provider.storage()).unwrap();
5045
5046 let credential = CredentialWithKey {
5047 credential: Credential::new(
5048 CredentialType::Basic,
5049 address.to_string().as_bytes().to_vec(),
5050 ),
5051 signature_key: keys.public().into(),
5052 };
5053 let bytes = KeyPackage::builder()
5054 .key_package_lifetime(lifetime)
5055 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
5056 .unwrap()
5057 .key_package()
5058 .tls_serialize_detached()
5059 .unwrap();
5060
5061 (address.to_string(), bytes)
5062 }
5063
5064 fn now_secs() -> u64 {
5065 std::time::SystemTime::now()
5066 .duration_since(std::time::UNIX_EPOCH)
5067 .unwrap()
5068 .as_secs()
5069 }
5070
5071 #[test]
5077 fn a_window_that_has_not_started_by_our_clock_is_reported_as_such() {
5078 let (manager, _) = create_addressed_manager("alice");
5079 let year = 365 * 24 * 60 * 60;
5080 let (peer, bytes) = key_package_with_window(
5081 "bob",
5082 Lifetime::init(
5083 now_secs() + year - 3600,
5084 now_secs() + year + 30 * 24 * 60 * 60,
5085 ),
5086 );
5087
5088 let err = manager
5089 .import_key_package(&peer, &bytes)
5090 .expect_err("a window that starts in a year is not valid now");
5091 assert!(
5092 matches!(
5093 err,
5094 MlsError::KeyPackageOutsideValidityWindow { expired: false }
5095 ),
5096 "refused for the wrong reason: {err:?}"
5097 );
5098 }
5099
5100 #[test]
5103 fn a_window_that_closed_by_our_clock_is_reported_as_such() {
5104 let (manager, _) = create_addressed_manager("alice");
5105 let day = 24 * 60 * 60;
5106 let (peer, bytes) = key_package_with_window(
5107 "bob",
5108 Lifetime::init(now_secs() - 60 * day, now_secs() - 30 * day),
5109 );
5110
5111 let err = manager
5112 .import_key_package(&peer, &bytes)
5113 .expect_err("a window that closed a month ago is not valid now");
5114 assert!(
5115 matches!(
5116 err,
5117 MlsError::KeyPackageOutsideValidityWindow { expired: true }
5118 ),
5119 "refused for the wrong reason: {err:?}"
5120 );
5121 }
5122
5123 #[test]
5127 fn only_the_window_refusal_changes_type() {
5128 let (manager, _) = create_addressed_manager("alice");
5129 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5130 let err = manager.import_key_package(&peer, &bytes).unwrap_err();
5131 assert!(matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")));
5132
5133 let err = manager
5134 .import_key_package(&peer, b"not a key package")
5135 .unwrap_err();
5136 assert!(matches!(err, MlsError::InvalidKeyPackage(_)), "{err:?}");
5137 }
5138
5139 #[test]
5142 fn a_year_long_validity_window_is_refused_at_import() {
5143 let (manager, _) = create_addressed_manager("alice");
5144
5145 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5146
5147 let err = manager
5148 .import_key_package(&peer, &bytes)
5149 .expect_err("a year-long window must be refused");
5150 assert!(
5151 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5152 "refused for the wrong reason: {err:?}"
5153 );
5154 }
5155
5156 #[test]
5162 fn a_wide_window_written_straight_into_the_store_is_refused_on_read() {
5163 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
5164 let (keys, address) = test_identity("alice");
5165 seed_identity(&storage, &keys);
5166 let manager = MlsManager::new(address.to_string(), Arc::clone(&storage)).unwrap();
5167
5168 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5169 storage
5170 .store(StorageKeyType::ContactKeyPackage.as_str(), &peer, &bytes)
5171 .unwrap();
5172
5173 let err = manager
5174 .get_contact_key_package(&peer)
5175 .expect_err("a cached year-long window must be refused too");
5176 assert!(
5177 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5178 "refused for the wrong reason: {err:?}"
5179 );
5180 }
5181
5182 #[test]
5189 fn our_own_key_package_states_the_lifetime_we_document() {
5190 let (manager, _) = create_addressed_manager("alice");
5191
5192 let bundle = manager.generate_key_package().unwrap();
5193 let key_package = KeyPackageIn::tls_deserialize_exact(bundle.key_package_data.as_slice())
5194 .unwrap()
5195 .validate(manager.provider.crypto(), ProtocolVersion::Mls10)
5196 .unwrap();
5197
5198 let window = key_package.life_time();
5199 assert_eq!(
5200 window.not_after() - window.not_before(),
5201 DEFAULT_KEY_PACKAGE_LIFETIME_SECS + LIFETIME_SKEW_MARGIN_SECS,
5202 "the window on the wire is not the lifetime this crate declares"
5203 );
5204 assert!(
5205 MlsManager::verify_lifetime_bound(&key_package).is_ok(),
5206 "this install mints a package it would refuse from a peer"
5207 );
5208 }
5209
5210 #[test]
5218 fn a_year_long_validity_window_is_refused_by_a_group_invite() {
5219 let alice = create_test_manager("alice");
5220 let group_id = alice.create_group("Test Group").unwrap().group_id;
5221
5222 let (bob, wide) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5223 let err = alice
5224 .add_group_member(&group_id, &bob, &wide)
5225 .expect_err("a year-long window must be refused by the group path too");
5226 assert!(
5227 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5228 "refused for the wrong reason: {err:?}"
5229 );
5230
5231 let bob_manager = create_test_manager("bob");
5234 let bob_kp = bob_manager.generate_key_package().unwrap();
5235 alice
5236 .add_group_member(&group_id, &bob, &bob_kp.key_package_data)
5237 .expect("a package inside the cap is admitted");
5238 }
5239
5240 #[test]
5243 fn a_window_exactly_at_the_cap_is_admitted_and_one_second_over_is_not() {
5244 let cap = MAX_ACCEPTED_KEY_PACKAGE_LIFETIME.as_secs();
5245 let (manager, _) = create_addressed_manager("alice");
5246
5247 let start = now_secs() - 10;
5250
5251 let (at_cap, bytes) = key_package_with_window("bob", Lifetime::init(start, start + cap));
5252 manager
5253 .import_key_package(&at_cap, &bytes)
5254 .expect("a window exactly at the cap is admitted");
5255
5256 let (over_cap, bytes) =
5257 key_package_with_window("carol", Lifetime::init(start, start + cap + 1));
5258 let err = manager
5259 .import_key_package(&over_cap, &bytes)
5260 .expect_err("one second over the cap must be refused");
5261 assert!(
5262 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5263 "refused for the wrong reason: {err:?}"
5264 );
5265 }
5266}