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 last_seal_mark(&self, other_user_id: &str) -> Option<crate::session::SealMark> {
1091 self.session_manager.last_seal_mark(other_user_id)
1092 }
1093
1094 pub fn note_acknowledged_seal(&self, other_user_id: &str, mark: crate::session::SealMark) {
1097 self.session_manager
1098 .note_acknowledged_seal(other_user_id, mark);
1099 }
1100
1101 pub fn unacknowledged_seals(&self, other_user_id: &str) -> u64 {
1104 self.session_manager.unacknowledged_seals(other_user_id)
1105 }
1106
1107 pub fn encrypt_for_existing_session(
1114 &self,
1115 other_user_id: &str,
1116 plaintext: &[u8],
1117 ) -> Result<EncryptedMessage> {
1118 let signature_keys = self.get_signer()?;
1119 self.session_manager
1120 .encrypt_message(other_user_id, plaintext, &signature_keys)
1121 }
1122
1123 pub fn get_pending_welcome(&self, other_user_id: &str) -> Result<Option<WelcomeMessage>> {
1125 let key_type = StorageKeyType::PendingWelcome.as_str();
1126 match self.storage.load(key_type, other_user_id)? {
1127 Some(data) => {
1128 let welcome: WelcomeMessage = serde_json::from_slice(&data)
1129 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1130 Ok(Some(welcome))
1131 }
1132 None => Ok(None),
1133 }
1134 }
1135
1136 pub fn clear_pending_welcome(&self, other_user_id: &str) -> Result<()> {
1138 let key_type = StorageKeyType::PendingWelcome.as_str();
1139 self.storage.delete(key_type, other_user_id)?;
1140 Ok(())
1141 }
1142
1143 pub fn decrypt_from_user(
1150 &self,
1151 encrypted: &EncryptedMessage,
1152 claimed_sender: &str,
1153 ) -> Result<Option<Vec<u8>>> {
1154 self.session_manager
1155 .decrypt_message(encrypted, claimed_sender)
1156 }
1157
1158 pub fn list_sessions(&self) -> Result<Vec<String>> {
1160 self.session_manager.list_sessions()
1161 }
1162
1163 pub fn delete_session(&self, other_user_id: &str) -> Result<()> {
1170 let session_id = self.session_manager.get_session_id(other_user_id)?;
1171 self.storage
1172 .delete(StorageKeyType::GroupMetadata.as_str(), session_id.as_str())?;
1173 self.session_manager.delete_session(other_user_id)
1174 }
1175
1176 pub fn create_group(&self, group_name: &str) -> Result<GroupInfo> {
1182 let group_id = GroupId::new(format!("group:{}", Uuid::new_v4()))?;
1183 let credential = self.get_credential()?;
1184 let signature_keys = self.get_signer()?;
1185
1186 let group = self
1187 .group_manager
1188 .create_group(&group_id, &credential, &signature_keys)?;
1189
1190 let metadata = GroupMetadata::new_with_creator(Some(group_name.to_string()), &self.user_id);
1192 self.save_group_metadata(&group_id, &metadata)?;
1193
1194 let mut info = self.group_manager.get_group_info(&group, &group_id);
1195 info.name = metadata.name;
1196 info.created_at_ms = metadata.created_at_ms;
1197 info.last_activity_ms = metadata.last_activity_ms;
1198
1199 info!(group_id = %group_id, name = %group_name, "Created new group");
1200 Ok(info)
1201 }
1202
1203 pub fn add_group_member(
1225 &self,
1226 group_id: &GroupId,
1227 invitee_user_id: &str,
1228 member_key_package: &[u8],
1229 ) -> Result<(WelcomeMessage, EncryptedMessage)> {
1230 let key_package = self.validate_peer_key_package(member_key_package)?;
1231
1232 Self::verify_credential_identity(&key_package, invitee_user_id)?;
1233 Self::verify_address_binding(&key_package, invitee_user_id)?;
1234 Self::verify_lifetime_bound(&key_package)?;
1235
1236 let mut group = self
1237 .group_manager
1238 .load_group(group_id)?
1239 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1240
1241 let signature_keys = self.get_signer()?;
1242 let (commit, welcome) =
1243 self.group_manager
1244 .add_member(&mut group, key_package, &signature_keys)?;
1245
1246 self.group_manager.save_group(group_id, &group)?;
1247
1248 let welcome_bytes = welcome
1249 .tls_serialize_detached()
1250 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1251
1252 let commit_bytes = commit
1253 .tls_serialize_detached()
1254 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1255
1256 let group_name = self.load_group_metadata(group_id)?.and_then(|m| m.name);
1258
1259 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
1260
1261 let welcome_msg = WelcomeMessage {
1262 group_id: group_id.clone(),
1263 welcome_data: welcome_bytes,
1264 inviter_id: self.user_id.clone(),
1265 group_name,
1266 timestamp_ms: now_ms,
1267 };
1268
1269 let commit_msg = EncryptedMessage {
1270 group_id: group_id.clone(),
1271 message_type: MlsMessageType::Commit,
1272 epoch: group.epoch().as_u64(),
1273 ciphertext: commit_bytes,
1274 sender_id: self.user_id.clone(),
1275 timestamp_ms: now_ms,
1276 };
1277
1278 Ok((welcome_msg, commit_msg))
1279 }
1280
1281 pub fn remove_group_member(
1308 &self,
1309 group_id: &GroupId,
1310 member_id: &str,
1311 ) -> Result<EncryptedMessage> {
1312 let mut group = self
1313 .group_manager
1314 .load_group(group_id)?
1315 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1316
1317 let member_indices: Vec<LeafNodeIndex> = group
1318 .members()
1319 .filter(|m| m.credential.serialized_content() == member_id.as_bytes())
1320 .map(|m| m.index)
1321 .collect();
1322
1323 if member_indices.is_empty() {
1324 return Err(MlsError::UserNotInGroup(member_id.to_string()));
1325 }
1326
1327 let signature_keys = self.get_signer()?;
1328 let commit =
1329 self.group_manager
1330 .remove_member(&mut group, &member_indices, &signature_keys)?;
1331
1332 self.group_manager.save_group(group_id, &group)?;
1333
1334 let ciphertext = commit
1335 .tls_serialize_detached()
1336 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1337
1338 Ok(EncryptedMessage {
1339 group_id: group_id.clone(),
1340 message_type: MlsMessageType::Commit,
1341 epoch: group.epoch().as_u64(),
1342 ciphertext,
1343 sender_id: self.user_id.clone(),
1344 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1345 })
1346 }
1347
1348 pub fn leave_group(&self, group_id: &GroupId) -> Result<()> {
1350 self.group_manager.delete_group(group_id)?;
1351 info!(group_id = %group_id, "Left group");
1352 Ok(())
1353 }
1354
1355 pub fn encrypt_for_group(
1357 &self,
1358 group_id: &GroupId,
1359 plaintext: &[u8],
1360 ) -> Result<EncryptedMessage> {
1361 let mut group = self
1362 .group_manager
1363 .load_group(group_id)?
1364 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1365
1366 let signature_keys = self.get_signer()?;
1367 let mls_message =
1368 self.group_manager
1369 .encrypt_message(&mut group, plaintext, &signature_keys)?;
1370
1371 self.group_manager.save_group(group_id, &group)?;
1372
1373 let ciphertext = mls_message
1374 .tls_serialize_detached()
1375 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1376
1377 Ok(EncryptedMessage {
1378 group_id: group_id.clone(),
1379 message_type: MlsMessageType::Application,
1380 epoch: group.epoch().as_u64(),
1381 ciphertext,
1382 sender_id: self.user_id.clone(),
1383 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1384 })
1385 }
1386
1387 pub fn decrypt_from_group(
1395 &self,
1396 encrypted: &EncryptedMessage,
1397 claimed_sender: &str,
1398 ) -> Result<Option<Vec<u8>>> {
1399 let mut group = self
1400 .group_manager
1401 .load_group(&encrypted.group_id)?
1402 .ok_or_else(|| MlsError::GroupNotFound(encrypted.group_id.to_string()))?;
1403
1404 let mls_message = MlsMessageIn::tls_deserialize_exact(&encrypted.ciphertext)
1405 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1406
1407 let result = self.group_manager.decrypt_message(
1408 &mut group,
1409 &encrypted.group_id,
1410 mls_message,
1411 claimed_sender,
1412 )?;
1413
1414 self.group_manager.save_group(&encrypted.group_id, &group)?;
1415
1416 Ok(result)
1417 }
1418
1419 pub fn join_group(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1421 if welcome.group_id.as_str().starts_with("session:") {
1433 return Err(MlsError::ReservedSessionNamespace {
1434 group_id: welcome.group_id.to_string(),
1435 });
1436 }
1437
1438 let mls_msg = MlsMessageIn::tls_deserialize_exact(&welcome.welcome_data)
1439 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1440
1441 let welcome_msg = match mls_msg.extract() {
1442 MlsMessageBodyIn::Welcome(w) => w,
1443 _ => {
1444 return Err(MlsError::WelcomeProcessing(
1445 "Not a Welcome message".to_string(),
1446 ))
1447 }
1448 };
1449
1450 let group = self
1451 .group_manager
1452 .join_group(welcome_msg, &welcome.group_id)?;
1453 let mut info = self.group_manager.get_group_info(&group, &welcome.group_id);
1454 info.name = welcome.group_name.clone();
1455
1456 info!(group_id = %welcome.group_id, "Joined group");
1457 Ok(info)
1458 }
1459
1460 pub fn list_groups(&self) -> Result<Vec<GroupId>> {
1462 let all_groups = self.group_manager.list_groups()?;
1463 Ok(all_groups
1464 .into_iter()
1465 .filter(|g| !g.as_str().starts_with("session:"))
1466 .collect())
1467 }
1468
1469 pub fn get_group_info(&self, group_id: &GroupId) -> Result<Option<GroupInfo>> {
1471 let group = match self.group_manager.load_group(group_id)? {
1472 Some(g) => g,
1473 None => return Ok(None),
1474 };
1475
1476 let mut info = self.group_manager.get_group_info(&group, group_id);
1477
1478 if let Some(metadata) = self.load_group_metadata(group_id)? {
1480 info.name = metadata.name;
1481 info.created_at_ms = metadata.created_at_ms;
1482 info.last_activity_ms = metadata.last_activity_ms;
1483 }
1484
1485 Ok(Some(info))
1486 }
1487
1488 pub fn has_group(&self, group_id: &GroupId) -> Result<bool> {
1497 Ok(self.group_manager.load_group(group_id)?.is_some())
1498 }
1499
1500 pub fn set_group_name(&self, group_id: &GroupId, name: &str) -> Result<()> {
1502 let mut metadata = self
1503 .load_group_metadata(group_id)?
1504 .unwrap_or_else(|| GroupMetadata::new(None));
1505 metadata.name = Some(name.to_string());
1506 metadata.touch();
1507 self.save_group_metadata(group_id, &metadata)
1508 }
1509
1510 pub fn get_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1512 self.load_group_metadata(group_id)
1513 }
1514
1515 pub fn set_group_custom_metadata(
1517 &self,
1518 group_id: &GroupId,
1519 key: &str,
1520 value: &str,
1521 ) -> Result<()> {
1522 let mut metadata = self
1523 .load_group_metadata(group_id)?
1524 .unwrap_or_else(|| GroupMetadata::new(None));
1525 metadata.custom.insert(key.to_string(), value.to_string());
1526 metadata.touch();
1527 self.save_group_metadata(group_id, &metadata)
1528 }
1529
1530 pub fn remove_group_custom_metadata(&self, group_id: &GroupId, key: &str) -> Result<()> {
1532 let mut metadata = self
1533 .load_group_metadata(group_id)?
1534 .unwrap_or_else(|| GroupMetadata::new(None));
1535 metadata.custom.remove(key);
1536 metadata.touch();
1537 self.save_group_metadata(group_id, &metadata)
1538 }
1539
1540 pub fn set_member_role(
1542 &self,
1543 group_id: &GroupId,
1544 user_id: &str,
1545 role: GroupRole,
1546 ) -> Result<()> {
1547 let mut metadata = self
1548 .load_group_metadata(group_id)?
1549 .unwrap_or_else(|| GroupMetadata::new(None));
1550 metadata.set_role(user_id, role);
1551 metadata.touch();
1552 self.save_group_metadata(group_id, &metadata)
1553 }
1554
1555 pub fn set_enforce_admin_commits(&mut self, enforce: bool) {
1564 self.group_manager.set_enforce_admin_commits(enforce);
1565 }
1566
1567 pub fn set_group_creator(&self, group_id: &GroupId, creator_id: &str) -> Result<()> {
1581 let mut metadata = self
1582 .load_group_metadata(group_id)?
1583 .unwrap_or_else(|| GroupMetadata::new(None));
1584 if metadata.created_by.is_some() {
1585 return Ok(());
1586 }
1587 metadata.created_by = Some(creator_id.to_string());
1588 metadata.touch();
1589 self.save_group_metadata(group_id, &metadata)
1590 }
1591
1592 pub fn remove_member_role(&self, group_id: &GroupId, user_id: &str) -> Result<()> {
1594 let mut metadata = self
1595 .load_group_metadata(group_id)?
1596 .unwrap_or_else(|| GroupMetadata::new(None));
1597 metadata.remove_role(user_id);
1598 metadata.touch();
1599 self.save_group_metadata(group_id, &metadata)
1600 }
1601
1602 pub fn decrypt(
1612 &self,
1613 encrypted: &EncryptedMessage,
1614 claimed_sender: &str,
1615 ) -> Result<Option<Vec<u8>>> {
1616 if encrypted.group_id.as_str().starts_with("session:") {
1617 self.decrypt_from_user(encrypted, claimed_sender)
1618 } else {
1619 self.decrypt_from_group(encrypted, claimed_sender)
1620 }
1621 }
1622
1623 pub fn process_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1625 if welcome.group_id.as_str().starts_with("session:") {
1626 self.join_session(welcome)
1627 } else {
1628 self.join_group(welcome)
1629 }
1630 }
1631}
1632
1633impl MlsManager {
1634 fn load_stored_key_package(&self, package_id: &str) -> Result<Option<KeyPackageBundle>> {
1640 let key_type = StorageKeyType::KeyPackage.as_str();
1641 let data = match self.storage.load(key_type, package_id)? {
1642 Some(data) => data,
1643 None => return Ok(None),
1644 };
1645
1646 let mut bundle = match serde_json::from_slice::<KeyPackageBundle>(&data) {
1647 Ok(bundle) => bundle,
1648 Err(_) => {
1649 let legacy_bundle = KeyPackageBundle::new(
1650 package_id.to_string(),
1651 self.user_id.clone(),
1652 data,
1653 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
1654 );
1655 let serialized = serde_json::to_vec(&legacy_bundle)
1656 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1657 self.storage.store(key_type, package_id, &serialized)?;
1658 legacy_bundle
1659 }
1660 };
1661
1662 if bundle.is_expired() {
1663 if bundle.expired_past_grace(KEY_PACKAGE_PURGE_GRACE_SECS) {
1670 self.purge_key_package_material(package_id, &bundle)?;
1671 }
1672 return Ok(None);
1673 }
1674
1675 let needs_backfill = bundle.provider_hash_ref.is_none();
1681 let Some(hash_ref) = self.bundle_hash_ref(&bundle) else {
1682 warn!(
1683 package_id = %package_id,
1684 "Key package bytes no longer parse as a key package, pruning stale entry"
1685 );
1686 self.storage.delete(key_type, package_id)?;
1687 return Ok(None);
1688 };
1689 if !self.provider_has_init_key(&hash_ref) {
1690 warn!(
1691 package_id = %package_id,
1692 "Key package private key no longer in provider storage, pruning stale entry"
1693 );
1694 self.storage.delete(key_type, package_id)?;
1695 return Ok(None);
1696 }
1697
1698 if needs_backfill {
1699 if let Ok(bytes) = hash_ref.tls_serialize_detached() {
1700 bundle.provider_hash_ref = Some(bytes);
1701 let serialized = serde_json::to_vec(&bundle)
1702 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1703 self.storage.store(key_type, package_id, &serialized)?;
1704 }
1705 }
1706
1707 Ok(Some(bundle))
1708 }
1709
1710 fn purge_key_package_material(
1725 &self,
1726 package_id: &str,
1727 bundle: &KeyPackageBundle,
1728 ) -> Result<()> {
1729 if let Some(hash_ref) = self.bundle_hash_ref(bundle) {
1730 use openmls_traits::storage::StorageProvider;
1731 if let Err(e) = self.provider.storage().delete_key_package(&hash_ref) {
1732 warn!(
1733 package_id = %package_id,
1734 error = %e,
1735 "Failed to delete a key package's private init key; keeping the record to retry"
1736 );
1737 return Ok(());
1738 }
1739 }
1740
1741 self.storage
1742 .delete(StorageKeyType::KeyPackage.as_str(), package_id)?;
1743 debug!(package_id = %package_id, "Purged key package and its init key");
1744 Ok(())
1745 }
1746
1747 fn key_package_hash_ref(&self, key_package_data: &[u8]) -> Option<KeyPackageRef> {
1754 let kp_in = KeyPackageIn::tls_deserialize_exact(key_package_data).ok()?;
1755 let kp: KeyPackage = kp_in
1756 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
1757 .ok()?;
1758 kp.hash_ref(self.provider.crypto()).ok()
1759 }
1760
1761 fn bundle_hash_ref(&self, bundle: &KeyPackageBundle) -> Option<KeyPackageRef> {
1765 bundle
1766 .provider_hash_ref
1767 .as_deref()
1768 .and_then(|bytes| KeyPackageRef::tls_deserialize_exact(bytes).ok())
1769 .or_else(|| self.key_package_hash_ref(&bundle.key_package_data))
1770 }
1771
1772 fn provider_has_init_key(&self, hash_ref: &KeyPackageRef) -> bool {
1776 use openmls_traits::storage::StorageProvider;
1777 let found: std::result::Result<Option<openmls::key_packages::KeyPackageBundle>, _> =
1778 self.provider.storage().key_package(hash_ref);
1779 matches!(found, Ok(Some(_)))
1780 }
1781
1782 #[cfg(test)]
1787 fn is_key_package_usable(&self, key_package_data: &[u8]) -> bool {
1788 self.key_package_hash_ref(key_package_data)
1789 .map(|hash_ref| self.provider_has_init_key(&hash_ref))
1790 .unwrap_or(false)
1791 }
1792
1793 fn load_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1795 let key_type = StorageKeyType::GroupMetadata.as_str();
1796 match self.storage.load(key_type, group_id.as_str())? {
1797 Some(data) => {
1798 let mut metadata: GroupMetadata = serde_json::from_slice(&data)
1799 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1800 if metadata.roles.is_empty()
1802 && metadata
1803 .custom
1804 .keys()
1805 .any(|k| k.starts_with(GroupMetadata::LEGACY_ROLE_KEY_PREFIX))
1806 {
1807 metadata.migrate_legacy_roles();
1808 if let Err(e) = self.save_group_metadata(group_id, &metadata) {
1810 warn!(group_id = %group_id.as_str(), error = %e, "Failed to persist legacy role migration");
1811 }
1812 }
1813 Ok(Some(metadata))
1814 }
1815 None => Ok(None),
1816 }
1817 }
1818
1819 fn save_group_metadata(&self, group_id: &GroupId, metadata: &GroupMetadata) -> Result<()> {
1821 let key_type = StorageKeyType::GroupMetadata.as_str();
1822 let data =
1823 serde_json::to_vec(metadata).map_err(|e| MlsError::Serialization(e.to_string()))?;
1824 self.storage.store(key_type, group_id.as_str(), &data)?;
1825 Ok(())
1826 }
1827}
1828
1829impl MlsManager {
1834 pub fn update_keys(&self, group_id: &GroupId) -> Result<EncryptedMessage> {
1843 use openmls::treesync::LeafNodeParameters;
1844
1845 let mut group = self
1846 .group_manager
1847 .load_group(group_id)?
1848 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1849
1850 let signature_keys = self.get_signer()?;
1851
1852 let bundle = group
1853 .self_update(
1854 &self.provider,
1855 &signature_keys,
1856 LeafNodeParameters::default(),
1857 )
1858 .map_err(|e| MlsError::OpenMls(format!("Self-update failed: {}", e)))?;
1859
1860 let (commit, _welcome, _group_info) = bundle.into_contents();
1861
1862 group
1863 .merge_pending_commit(&self.provider)
1864 .map_err(|e| MlsError::OpenMls(format!("Failed to merge self-update commit: {}", e)))?;
1865
1866 self.group_manager.save_group(group_id, &group)?;
1867
1868 if let Some(mut metadata) = self.load_group_metadata(group_id)? {
1870 metadata.touch();
1871 self.save_group_metadata(group_id, &metadata)?;
1872 }
1873
1874 let ciphertext = commit
1875 .tls_serialize_detached()
1876 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1877
1878 debug!(group_id = %group_id, epoch = %group.epoch().as_u64(), "Updated group keys");
1879
1880 Ok(EncryptedMessage {
1881 group_id: group_id.clone(),
1882 message_type: MlsMessageType::Commit,
1883 epoch: group.epoch().as_u64(),
1884 ciphertext,
1885 sender_id: self.user_id.clone(),
1886 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1887 })
1888 }
1889
1890 pub fn ensure_min_key_packages(&self, min: usize) -> Result<usize> {
1915 let min = min.min(MAX_PUSH_KEY_PACKAGES);
1916 let key_type = StorageKeyType::KeyPackage.as_str();
1917 let package_ids = self.storage.list_keys(key_type)?;
1918
1919 let mut valid_count = 0;
1921 for package_id in &package_ids {
1922 if self
1923 .load_stored_key_package(package_id)?
1924 .is_some_and(|bundle| !bundle.withheld_from_hand_out())
1925 {
1926 valid_count += 1;
1927 }
1928 }
1929
1930 let to_generate = min.saturating_sub(valid_count);
1932 for _ in 0..to_generate {
1933 self.generate_key_package()?;
1934 valid_count += 1;
1935 }
1936
1937 debug!(
1938 valid_count = valid_count,
1939 generated = to_generate,
1940 "Ensured minimum key packages"
1941 );
1942
1943 Ok(valid_count)
1944 }
1945
1946 pub fn count_valid_key_packages(&self) -> Result<usize> {
1953 let key_type = StorageKeyType::KeyPackage.as_str();
1954 let package_ids = self.storage.list_keys(key_type)?;
1955
1956 let mut count = 0;
1957 for package_id in package_ids {
1958 if self.load_stored_key_package(&package_id)?.is_some() {
1959 count += 1;
1960 }
1961 }
1962
1963 Ok(count)
1964 }
1965
1966 pub fn get_identity_public_key(&self) -> Result<Vec<u8>> {
1975 let credential = self.get_credential()?;
1976 Ok(credential.signature_key.as_slice().to_vec())
1977 }
1978
1979 pub fn sign_data(&self, data: &[u8]) -> Result<Vec<u8>> {
1992 use openmls_traits::signatures::Signer;
1993
1994 let signer = self.get_signer()?;
1995 let signature = signer
1996 .sign(data)
1997 .map_err(|e| MlsError::Signing(format!("Failed to sign data: {:?}", e)))?;
1998 Ok(signature.as_slice().to_vec())
1999 }
2000
2001 pub fn verify_signature(public_key: &[u8], data: &[u8], signature: &[u8]) -> Result<bool> {
2013 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
2014
2015 let verifying_key = VerifyingKey::try_from(public_key).map_err(|e| {
2017 MlsError::InvalidPublicKey(format!("Invalid Ed25519 public key: {}", e))
2018 })?;
2019
2020 let sig = Signature::try_from(signature).map_err(|e| {
2022 MlsError::VerificationFailed(format!("Invalid signature format: {}", e))
2023 })?;
2024
2025 match verifying_key.verify(data, &sig) {
2027 Ok(()) => Ok(true),
2028 Err(_) => Ok(false),
2029 }
2030 }
2031
2032 pub fn derive_address(public_key: &[u8]) -> Result<Address> {
2058 offline_protocol_sealed::derive_address(public_key).map_err(MlsError::from)
2059 }
2060
2061 #[deprecated(
2081 note = "use derive_address; this returns the self-certifying off1… address, no longer base58"
2082 )]
2083 pub fn derive_user_id_from_public_key(public_key: &[u8]) -> String {
2084 use sha2::{Digest, Sha256};
2085
2086 let hash = Sha256::digest(public_key);
2087 let mut truncated = [0u8; Address::HASH_LEN];
2088 truncated.copy_from_slice(&hash[..Address::HASH_LEN]);
2089 Address::from_hash_bytes(truncated).to_string()
2090 }
2091}
2092
2093#[cfg(feature = "test-utils")]
2102impl MlsManager {
2103 pub fn seat_forged_leaf_for_testing(
2117 &self,
2118 group_id: &GroupId,
2119 impersonated: &str,
2120 ) -> Result<Vec<u8>> {
2121 let forged_storage: Arc<dyn MlsStorage> = Arc::new(crate::storage::InMemoryStorage::new());
2122 let forged_provider = MlsProvider::new(MlsStorageAdapter::new(forged_storage));
2123 let forged_keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
2124 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
2125 forged_keys
2126 .store(forged_provider.storage())
2127 .map_err(|e| MlsError::CryptoGeneration(format!("storing forged key: {:?}", e)))?;
2128
2129 let credential = CredentialWithKey {
2130 credential: Credential::new(CredentialType::Basic, impersonated.as_bytes().to_vec()),
2131 signature_key: forged_keys.public().into(),
2132 };
2133 let key_package = KeyPackage::builder()
2134 .build(
2135 DEFAULT_CIPHERSUITE,
2136 &forged_provider,
2137 &forged_keys,
2138 credential,
2139 )
2140 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?
2141 .key_package()
2142 .tls_serialize_detached()
2143 .map_err(|e| MlsError::Serialization(e.to_string()))?;
2144
2145 let validated = KeyPackageIn::tls_deserialize_exact(&key_package)
2146 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?
2147 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
2148 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
2149
2150 let mut group = self
2151 .group_manager
2152 .load_group(group_id)?
2153 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
2154 let signer = self.get_signer()?;
2155 let (commit, _welcome) = self
2156 .group_manager
2157 .add_member(&mut group, validated, &signer)?;
2158 self.group_manager.save_group(group_id, &group)?;
2159
2160 commit
2161 .tls_serialize_detached()
2162 .map_err(|e| MlsError::Serialization(e.to_string()))
2163 }
2164
2165 pub fn key_package_with_window_for_testing(
2173 claimed: &str,
2174 not_before: u64,
2175 not_after: u64,
2176 ) -> Result<Vec<u8>> {
2177 let storage: Arc<dyn MlsStorage> = Arc::new(crate::storage::InMemoryStorage::new());
2178 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
2179 let keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
2180 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
2181 keys.store(provider.storage())
2182 .map_err(|e| MlsError::CryptoGeneration(format!("storing key: {:?}", e)))?;
2183 let credential = CredentialWithKey {
2184 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
2185 signature_key: keys.public().into(),
2186 };
2187 KeyPackage::builder()
2188 .key_package_lifetime(Lifetime::init(not_before, not_after))
2189 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
2190 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?
2191 .key_package()
2192 .tls_serialize_detached()
2193 .map_err(|e| MlsError::Serialization(e.to_string()))
2194 }
2195}
2196
2197pub fn is_mls_framed(bytes: &[u8]) -> bool {
2206 MlsMessageIn::tls_deserialize_exact(bytes).is_ok()
2207}
2208
2209#[cfg(test)]
2210mod tests {
2211 use super::*;
2212 use crate::storage::InMemoryStorage;
2213
2214 fn create_test_manager(label: &str) -> MlsManager {
2223 create_addressed_manager(label).0
2224 }
2225
2226 fn addr(label: &str) -> String {
2228 test_identity(label).1.to_string()
2229 }
2230
2231 fn slot(a: &str, b: &str) -> GroupId {
2237 GroupId::for_session(&addr(a), &addr(b)).unwrap()
2238 }
2239
2240 pub(crate) fn test_identity(label: &str) -> (SignatureKeyPair, Address) {
2248 use sha2::{Digest, Sha256};
2249
2250 let seed: [u8; 32] = Sha256::digest(label.as_bytes()).into();
2251 let signing = ed25519_dalek::SigningKey::from_bytes(&seed);
2252 let public = signing.verifying_key().to_bytes().to_vec();
2253 let address = MlsManager::derive_address(&public).unwrap();
2254 let keys = SignatureKeyPair::from_raw(
2255 DEFAULT_CIPHERSUITE.signature_algorithm(),
2256 signing.to_bytes().to_vec(),
2257 public,
2258 );
2259 (keys, address)
2260 }
2261
2262 pub(crate) fn seed_identity(storage: &Arc<dyn MlsStorage>, keys: &SignatureKeyPair) {
2265 storage
2266 .store(
2267 StorageKeyType::Identity.as_str(),
2268 "key_pair",
2269 &serde_json::to_vec(keys).unwrap(),
2270 )
2271 .unwrap();
2272 }
2273
2274 fn create_addressed_manager(label: &str) -> (MlsManager, Address) {
2277 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2278 let (keys, address) = test_identity(label);
2279 seed_identity(&storage, &keys);
2280 let manager = MlsManager::new(address.to_string(), storage).unwrap();
2281 (manager, address)
2282 }
2283
2284 fn create_test_manager_with_storage(user_id: &str) -> (MlsManager, Arc<InMemoryStorage>) {
2288 let storage = Arc::new(InMemoryStorage::new());
2289 let manager = MlsManager::new(user_id, storage.clone()).unwrap();
2290 (manager, storage)
2291 }
2292
2293 fn expire_package(storage: &InMemoryStorage, package_id: &str, secs_ago: u64) {
2295 let key_type = StorageKeyType::KeyPackage.as_str();
2296 let data = storage
2297 .load(key_type, package_id)
2298 .unwrap()
2299 .expect("package to age is not in storage");
2300 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2301 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
2302 bundle.expires_at_ms = now_ms - (secs_ago * 1000);
2303 storage
2304 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2305 .unwrap();
2306 }
2307
2308 fn init_key_present(manager: &MlsManager, bundle: &KeyPackageBundle) -> bool {
2310 manager.is_key_package_usable(&bundle.key_package_data)
2311 }
2312
2313 fn strip_cached_ref(storage: &InMemoryStorage, package_id: &str) {
2316 let key_type = StorageKeyType::KeyPackage.as_str();
2317 let data = storage
2318 .load(key_type, package_id)
2319 .unwrap()
2320 .expect("package to strip is not in storage");
2321 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2322 bundle.provider_hash_ref = None;
2323 storage
2324 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2325 .unwrap();
2326 }
2327
2328 #[test]
2333 fn test_minted_package_carries_its_provider_hash_ref() {
2334 let manager = create_test_manager("alice");
2335 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2336
2337 let cached = bundle
2338 .provider_hash_ref
2339 .as_deref()
2340 .expect("mint must stamp the provider hash ref");
2341 let derived = manager
2342 .key_package_hash_ref(&bundle.key_package_data)
2343 .expect("freshly minted package bytes must parse")
2344 .tls_serialize_detached()
2345 .unwrap();
2346 assert_eq!(
2347 cached,
2348 &derived[..],
2349 "cached ref disagrees with the one derived from the package bytes"
2350 );
2351 }
2352
2353 #[test]
2356 fn test_record_without_cached_ref_is_backfilled_on_first_load() {
2357 let (manager, storage) = create_test_manager_with_storage("alice");
2358 let minted = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2359 strip_cached_ref(&storage, &minted.package_id);
2360
2361 let loaded = manager
2362 .key_package_by_id(&minted.package_id)
2363 .unwrap()
2364 .expect("a record without a cached ref must still load");
2365 assert_eq!(
2366 loaded.provider_hash_ref, minted.provider_hash_ref,
2367 "load must hand back a bundle stamped with the same ref the mint computed"
2368 );
2369
2370 let stored: KeyPackageBundle = serde_json::from_slice(
2371 &storage
2372 .load(StorageKeyType::KeyPackage.as_str(), &minted.package_id)
2373 .unwrap()
2374 .unwrap(),
2375 )
2376 .unwrap();
2377 assert_eq!(
2378 stored.provider_hash_ref, minted.provider_hash_ref,
2379 "the backfilled ref must be persisted, not only returned"
2380 );
2381 }
2382
2383 #[test]
2386 fn test_purge_destroys_init_key_for_a_record_without_cached_ref() {
2387 let (manager, storage) = create_test_manager_with_storage("alice");
2388 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2389 strip_cached_ref(&storage, &bundle.package_id);
2390 expire_package(
2391 &storage,
2392 &bundle.package_id,
2393 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2394 );
2395
2396 assert!(manager
2397 .key_package_by_id(&bundle.package_id)
2398 .unwrap()
2399 .is_none());
2400 assert!(
2401 !init_key_present(&manager, &bundle),
2402 "the private init key outlived a pre-cache package"
2403 );
2404 }
2405
2406 #[test]
2411 fn test_expired_package_is_withdrawn_but_its_init_key_survives_the_grace_window() {
2412 let (manager, storage) = create_test_manager_with_storage("alice");
2413 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2414
2415 expire_package(&storage, &bundle.package_id, 60);
2416
2417 assert!(
2418 manager
2419 .key_package_by_id(&bundle.package_id)
2420 .unwrap()
2421 .is_none(),
2422 "an expired package must not be handed out"
2423 );
2424 assert!(
2425 init_key_present(&manager, &bundle),
2426 "the init key was destroyed on the expiry edge, breaking a late Welcome"
2427 );
2428
2429 let replacement = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2431 assert_ne!(replacement.package_id, bundle.package_id);
2432 }
2433
2434 #[test]
2439 fn test_package_past_the_grace_window_has_its_init_key_destroyed() {
2440 let (manager, storage) = create_test_manager_with_storage("alice");
2441 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2442 assert!(init_key_present(&manager, &bundle));
2443
2444 expire_package(
2445 &storage,
2446 &bundle.package_id,
2447 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2448 );
2449
2450 assert!(manager
2452 .key_package_by_id(&bundle.package_id)
2453 .unwrap()
2454 .is_none());
2455
2456 assert!(
2457 !init_key_present(&manager, &bundle),
2458 "the private init key outlived its package"
2459 );
2460 assert!(
2461 storage
2462 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2463 .unwrap()
2464 .is_none(),
2465 "the bundle record outlived its key material"
2466 );
2467 }
2468
2469 #[test]
2473 fn test_delete_key_package_destroys_the_init_key() {
2474 let manager = create_test_manager("alice");
2475 let bundle = manager.generate_publication_key_package().unwrap();
2476 assert!(init_key_present(&manager, &bundle));
2477
2478 manager.delete_key_package(&bundle.package_id).unwrap();
2479
2480 assert!(
2481 !init_key_present(&manager, &bundle),
2482 "delete_key_package left the private init key in provider storage"
2483 );
2484 }
2485
2486 #[test]
2491 fn test_delete_key_package_destroys_the_init_key_of_a_legacy_record() {
2492 let (manager, storage) = create_test_manager_with_storage("alice");
2493 let bundle = manager.generate_publication_key_package().unwrap();
2494 storage
2496 .store(
2497 StorageKeyType::KeyPackage.as_str(),
2498 &bundle.package_id,
2499 &bundle.key_package_data,
2500 )
2501 .unwrap();
2502
2503 manager.delete_key_package(&bundle.package_id).unwrap();
2504
2505 assert!(
2506 !init_key_present(&manager, &bundle),
2507 "a legacy record's private init key survived its deletion"
2508 );
2509 assert!(
2510 storage
2511 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2512 .unwrap()
2513 .is_none(),
2514 "the legacy record outlived its key material"
2515 );
2516 }
2517
2518 #[test]
2522 fn test_delete_key_package_removes_an_unreadable_record() {
2523 let (manager, storage) = create_test_manager_with_storage("alice");
2524 storage
2525 .store(
2526 StorageKeyType::KeyPackage.as_str(),
2527 "junk",
2528 b"not a package",
2529 )
2530 .unwrap();
2531
2532 manager.delete_key_package("junk").unwrap();
2533
2534 assert!(storage
2535 .load(StorageKeyType::KeyPackage.as_str(), "junk")
2536 .unwrap()
2537 .is_none());
2538 }
2539
2540 #[test]
2546 fn test_ensure_min_key_packages_is_capped_at_the_pool_ceiling() {
2547 let manager = create_test_manager("alice");
2548
2549 let count = manager
2550 .ensure_min_key_packages(MAX_PUSH_KEY_PACKAGES + 50)
2551 .unwrap();
2552
2553 assert_eq!(count, MAX_PUSH_KEY_PACKAGES);
2554 assert_eq!(
2555 manager.count_valid_key_packages().unwrap(),
2556 MAX_PUSH_KEY_PACKAGES,
2557 "pre-filling minted past the ceiling the push path enforces"
2558 );
2559 }
2560
2561 #[test]
2562 fn test_manager_creation() {
2563 let manager = create_test_manager("alice");
2564 assert_eq!(manager.user_id(), addr("alice"));
2565 }
2566
2567 #[test]
2568 fn test_key_package_generation() {
2569 let manager = create_test_manager("alice");
2570 let package = manager.generate_key_package().unwrap();
2571
2572 assert_eq!(package.user_id, addr("alice"));
2573 assert!(!package.key_package_data.is_empty());
2574 assert!(!package.is_expired());
2575 }
2576
2577 #[test]
2582 fn test_get_or_create_never_returns_a_publication_package() {
2583 let manager = create_test_manager("alice");
2584 let reserved = manager.generate_publication_key_package().unwrap();
2585 assert!(reserved.reserved_for_publication);
2586
2587 let pushed = manager.get_or_create_key_package().unwrap();
2588 assert_ne!(
2589 pushed.package_id, reserved.package_id,
2590 "the push path took the package a published record is standing on"
2591 );
2592 assert!(!pushed.reserved_for_publication);
2593
2594 let pushed_again = manager.get_or_create_key_package().unwrap();
2597 assert_eq!(pushed.package_id, pushed_again.package_id);
2598 }
2599
2600 #[test]
2604 fn test_key_package_by_id_reports_presence() {
2605 let manager = create_test_manager("alice");
2606 let bundle = manager.generate_publication_key_package().unwrap();
2607
2608 let found = manager.key_package_by_id(&bundle.package_id).unwrap();
2609 assert!(found.is_some());
2610 assert_eq!(found.unwrap().package_id, bundle.package_id);
2611
2612 assert!(manager
2613 .key_package_by_id("no-such-package")
2614 .unwrap()
2615 .is_none());
2616 }
2617
2618 #[test]
2621 fn test_publication_packages_are_distinct_per_slot() {
2622 let manager = create_test_manager("alice");
2623 let a = manager.generate_publication_key_package().unwrap();
2624 let b = manager.generate_publication_key_package().unwrap();
2625 assert_ne!(a.package_id, b.package_id);
2626 assert_ne!(a.key_package_data, b.key_package_data);
2627 }
2628
2629 #[test]
2630 fn test_get_or_create_key_package() {
2631 let manager = create_test_manager("alice");
2632 let pkg1 = manager.get_or_create_key_package().unwrap();
2633 let pkg2 = manager.get_or_create_key_package().unwrap();
2634 assert_eq!(pkg1.package_id, pkg2.package_id);
2638 }
2639
2640 #[test]
2650 fn test_push_path_gives_each_peer_its_own_key_package() {
2651 let manager = create_test_manager("alice");
2652
2653 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap();
2654 let for_carol = manager.take_push_key_package(&addr("carol")).unwrap();
2655
2656 assert_ne!(
2657 for_bob.bundle.package_id, for_carol.bundle.package_id,
2658 "two peers were advertised the same key package"
2659 );
2660 assert_ne!(
2661 for_bob.bundle.key_package_data, for_carol.bundle.key_package_data,
2662 "two peers were advertised the same init key"
2663 );
2664 assert!(!for_bob.pool_exhausted && !for_carol.pool_exhausted);
2665 assert_eq!(
2666 for_bob.bundle.assigned_peer.as_deref(),
2667 Some(addr("bob").as_str())
2668 );
2669 assert_eq!(
2670 for_carol.bundle.assigned_peer.as_deref(),
2671 Some(addr("carol").as_str())
2672 );
2673 }
2674
2675 #[test]
2680 fn test_push_path_reuses_one_package_for_repeat_pushes_to_a_peer() {
2681 let manager = create_test_manager("alice");
2682
2683 let first = manager.take_push_key_package(&addr("bob")).unwrap();
2684 let second = manager.take_push_key_package(&addr("bob")).unwrap();
2685 let third = manager.take_push_key_package(&addr("bob")).unwrap();
2686
2687 assert_eq!(first.bundle.package_id, second.bundle.package_id);
2688 assert_eq!(first.bundle.package_id, third.bundle.package_id);
2689 assert_eq!(
2690 manager.count_valid_key_packages().unwrap(),
2691 1,
2692 "repeat pushes to one peer minted extra key material"
2693 );
2694 }
2695
2696 #[test]
2701 fn test_push_path_mints_a_fresh_package_after_the_peer_consumed_one() {
2702 let alice = create_test_manager("alice");
2703 let bob = create_test_manager("bob");
2704
2705 let advertised = alice.take_push_key_package(&addr("bob")).unwrap().bundle;
2706 bob.import_key_package(&addr("alice"), &advertised.key_package_data)
2707 .unwrap();
2708 let welcome = bob.create_session(&addr("alice")).unwrap();
2709 alice.join_session(&welcome).unwrap();
2710
2711 let next = alice.take_push_key_package(&addr("bob")).unwrap();
2712 assert_ne!(
2713 next.bundle.package_id, advertised.package_id,
2714 "a consumed package was advertised again"
2715 );
2716 assert!(!next.pool_exhausted);
2717 }
2718
2719 #[test]
2724 fn test_two_peers_can_both_establish_sessions_after_being_pushed_to() {
2725 let alice = create_test_manager("alice");
2726 let bob = create_test_manager("bob");
2727 let carol = create_test_manager("carol");
2728
2729 for (peer, manager) in [(addr("bob"), &bob), (addr("carol"), &carol)] {
2730 let advertised = alice.take_push_key_package(&peer).unwrap().bundle;
2731 manager
2732 .import_key_package(&addr("alice"), &advertised.key_package_data)
2733 .unwrap();
2734 }
2735
2736 let from_bob = bob.create_session(&addr("alice")).unwrap();
2739 let from_carol = carol.create_session(&addr("alice")).unwrap();
2740
2741 alice.join_session(&from_bob).unwrap();
2742 alice
2743 .join_session(&from_carol)
2744 .expect("second peer's Welcome must still be processable");
2745
2746 assert!(alice.has_session(&addr("bob")).unwrap());
2747 assert!(alice.has_session(&addr("carol")).unwrap());
2748 }
2749
2750 #[test]
2753 fn test_push_path_never_takes_a_publication_package() {
2754 let manager = create_test_manager("alice");
2755 let reserved = manager.generate_publication_key_package().unwrap();
2756
2757 for peer in [addr("bob"), addr("carol"), addr("dave")] {
2758 let taken = manager.take_push_key_package(&peer).unwrap();
2759 assert_ne!(
2760 taken.bundle.package_id, reserved.package_id,
2761 "the push path took the package a published record stands on"
2762 );
2763 }
2764 }
2765
2766 #[test]
2770 fn test_get_or_create_never_returns_a_peer_assigned_package() {
2771 let manager = create_test_manager("alice");
2772 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2773
2774 let unclaimed = manager.get_or_create_key_package().unwrap();
2775 assert_ne!(unclaimed.package_id, for_bob.package_id);
2776 assert!(unclaimed.assigned_peer.is_none());
2777 }
2778
2779 fn record_present(storage: &InMemoryStorage, package_id: &str) -> bool {
2781 storage
2782 .load(StorageKeyType::KeyPackage.as_str(), package_id)
2783 .unwrap()
2784 .is_some()
2785 }
2786
2787 #[test]
2791 fn test_marking_synced_keeps_the_record_and_the_init_key() {
2792 let (mut manager, storage) = create_test_manager_with_storage("alice");
2793 let bundle = manager.generate_key_package().unwrap();
2794
2795 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2796
2797 assert!(
2798 record_present(&storage, &bundle.package_id),
2799 "marking synced deleted the record, so nothing can ever expire the key"
2800 );
2801 assert!(
2802 init_key_present(&manager, &bundle),
2803 "marking synced destroyed the init key a Welcome from the upload needs"
2804 );
2805 let reread = manager
2806 .key_package_by_id(&bundle.package_id)
2807 .unwrap()
2808 .expect("a synced package is still live");
2809 assert!(reread.synced);
2810 }
2811
2812 #[test]
2816 fn test_unclaimed_synced_package_has_its_init_key_destroyed_past_the_grace_window() {
2817 let (mut manager, storage) = create_test_manager_with_storage("alice");
2818 let bundle = manager.generate_key_package().unwrap();
2819 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2820
2821 expire_package(
2822 &storage,
2823 &bundle.package_id,
2824 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2825 );
2826 assert!(manager
2827 .key_package_by_id(&bundle.package_id)
2828 .unwrap()
2829 .is_none());
2830
2831 assert!(
2832 !init_key_present(&manager, &bundle),
2833 "a synced package's init key outlived its grace window"
2834 );
2835 assert!(!record_present(&storage, &bundle.package_id));
2836 }
2837
2838 #[test]
2841 fn test_synced_package_is_withheld_from_both_hand_out_paths() {
2842 let mut manager = create_test_manager("alice");
2843 let synced = manager.generate_key_package().unwrap();
2844 manager.mark_key_package_synced(&synced.package_id).unwrap();
2845
2846 let pushed = manager.take_push_key_package(&addr("bob")).unwrap();
2847 assert_ne!(pushed.bundle.package_id, synced.package_id);
2848 assert!(!pushed.pool_exhausted);
2849
2850 let peerless = manager.get_or_create_key_package().unwrap();
2851 assert_ne!(peerless.package_id, synced.package_id);
2852 }
2853
2854 #[test]
2858 fn test_pending_list_holds_only_packages_the_application_may_publish() {
2859 let mut manager = create_test_manager("alice");
2860 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2863 let plain = manager.generate_key_package().unwrap();
2864 let reserved = manager.generate_publication_key_package().unwrap();
2865 let synced = manager.generate_key_package().unwrap();
2866 manager.mark_key_package_synced(&synced.package_id).unwrap();
2867
2868 let pending: Vec<String> = manager
2869 .get_pending_key_packages()
2870 .unwrap()
2871 .into_iter()
2872 .map(|b| b.package_id)
2873 .collect();
2874
2875 assert!(pending.contains(&plain.package_id));
2876 assert!(
2877 !pending.contains(&reserved.package_id),
2878 "lists a slot package"
2879 );
2880 assert!(
2881 !pending.contains(&for_bob.package_id),
2882 "lists a peer's package"
2883 );
2884 assert!(
2885 !pending.contains(&synced.package_id),
2886 "lists a synced package"
2887 );
2888 }
2889
2890 #[test]
2894 fn test_ensure_min_does_not_count_a_synced_package_toward_the_pool() {
2895 let mut manager = create_test_manager("alice");
2896 let synced = manager.generate_key_package().unwrap();
2897 manager.mark_key_package_synced(&synced.package_id).unwrap();
2898
2899 assert_eq!(manager.ensure_min_key_packages(1).unwrap(), 1);
2900 assert_eq!(
2901 manager.count_valid_key_packages().unwrap(),
2902 2,
2903 "a replacement was minted beside the synced package"
2904 );
2905 }
2906
2907 #[test]
2910 fn test_welcome_built_against_a_synced_package_still_opens() {
2911 let mut alice = create_test_manager("alice");
2912 let bob = create_test_manager("bob");
2913 let uploaded = alice.generate_key_package().unwrap();
2914 alice.mark_key_package_synced(&uploaded.package_id).unwrap();
2915
2916 bob.import_key_package(&addr("alice"), &uploaded.key_package_data)
2917 .unwrap();
2918 let welcome = bob.create_session(&addr("alice")).unwrap();
2919
2920 alice
2921 .join_session(&welcome)
2922 .expect("a Welcome against a synced package must open");
2923 assert!(alice.has_session(&addr("bob")).unwrap());
2924 }
2925
2926 #[test]
2929 fn test_marking_a_legacy_record_upgrades_and_marks_it() {
2930 let (mut manager, storage) = create_test_manager_with_storage("alice");
2931 let bundle = manager.generate_key_package().unwrap();
2932 storage
2933 .store(
2934 StorageKeyType::KeyPackage.as_str(),
2935 &bundle.package_id,
2936 &bundle.key_package_data,
2937 )
2938 .unwrap();
2939
2940 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2941
2942 let reread = manager
2943 .key_package_by_id(&bundle.package_id)
2944 .unwrap()
2945 .expect("the legacy package is still live");
2946 assert!(reread.synced);
2947 assert!(init_key_present(&manager, &bundle));
2948 }
2949
2950 #[test]
2953 fn test_marking_an_unknown_or_consumed_package_is_a_no_op() {
2954 let mut alice = create_test_manager("alice");
2955 alice.mark_key_package_synced("no-such-package").unwrap();
2956
2957 let bob = create_test_manager("bob");
2958 let advertised = alice.take_push_key_package(&addr("bob")).unwrap().bundle;
2959 bob.import_key_package(&addr("alice"), &advertised.key_package_data)
2960 .unwrap();
2961 let welcome = bob.create_session(&addr("alice")).unwrap();
2962 alice.join_session(&welcome).unwrap();
2963
2964 alice
2965 .mark_key_package_synced(&advertised.package_id)
2966 .unwrap();
2967 assert!(alice
2968 .key_package_by_id(&advertised.package_id)
2969 .unwrap()
2970 .is_none());
2971 }
2972
2973 #[test]
2988 fn only_the_known_sites_delete_a_key_package_record_alone() {
2989 let allowed = ["load_stored_key_package", "purge_key_package_material"];
2990 let (checked, offenders) = record_only_key_package_deletes(include_str!("manager.rs"));
2991
2992 assert!(
2993 checked >= 8,
2994 "found only {checked} key package functions; the scan is broken, not the code"
2995 );
2996 let offenders: Vec<_> = offenders
2997 .into_iter()
2998 .filter(|name| !allowed.contains(&name.as_str()))
2999 .collect();
3000 assert!(
3001 offenders.is_empty(),
3002 "record-only delete of a key package in {offenders:?}. Destroy the init \
3003 key too (purge_key_package_material), or keep the record"
3004 );
3005 }
3006
3007 #[test]
3012 fn the_record_delete_scanner_sees_every_function_header_and_crlf() {
3013 let source = [
3014 "impl MlsManager {",
3015 " fn purge_key_package_material(&self) {",
3016 " let t = StorageKeyType::KeyPackage.as_str();",
3017 " self.storage.delete(t, id)?;",
3018 " }",
3019 " pub(crate) fn hidden_crate(&self) {",
3020 " self.storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3021 " }",
3022 " fn load_stored_key_package(&self) {}",
3023 " pub async fn hidden_async(&self) {",
3024 " self.storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3025 " }",
3026 "}",
3027 "fn hidden_free() {",
3028 " storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3029 "}",
3030 "#[cfg(test)]",
3031 "mod tests {",
3032 " fn in_tests() {",
3033 " storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3034 " }",
3035 "}",
3036 ]
3037 .join("\r\n");
3038
3039 let (checked, offenders) = record_only_key_package_deletes(&source);
3040
3041 assert_eq!(checked, 4, "the test module is out of scope");
3042 assert_eq!(
3043 offenders,
3044 [
3045 "purge_key_package_material",
3046 "hidden_crate",
3047 "hidden_async",
3048 "hidden_free"
3049 ]
3050 );
3051 }
3052
3053 fn record_only_key_package_deletes(source: &str) -> (usize, Vec<String>) {
3058 let lines: Vec<&str> = source.lines().collect();
3059 let test_module = lines
3060 .windows(2)
3061 .position(|pair| pair[0].trim() == "#[cfg(test)]" && pair[1].starts_with("mod tests"))
3062 .expect("the source has a test module");
3063
3064 let mut functions: Vec<(String, String)> = Vec::new();
3065 for line in &lines[..test_module] {
3066 if let Some(name) = function_header_name(line) {
3067 functions.push((name.to_string(), String::new()));
3068 } else if let Some((_, body)) = functions.last_mut() {
3069 body.push_str(line);
3070 body.push('\n');
3071 }
3072 }
3073
3074 let mut checked = 0;
3075 let mut offenders = Vec::new();
3076 for (name, body) in functions {
3077 if !body.contains("StorageKeyType::KeyPackage.as_str()") {
3078 continue;
3079 }
3080 checked += 1;
3081 if body.contains(".delete(") {
3082 offenders.push(name);
3083 }
3084 }
3085 (checked, offenders)
3086 }
3087
3088 fn function_header_name(line: &str) -> Option<&str> {
3091 let trimmed = line.trim_start();
3092 if !matches!(line.len() - trimmed.len(), 0 | 4) {
3093 return None;
3094 }
3095 let mut rest = trimmed;
3096 loop {
3097 if let Some(after) = rest.strip_prefix("pub(") {
3098 rest = after.split_once(')')?.1.trim_start();
3099 } else if let Some(after) = ["pub ", "async ", "const ", "unsafe "]
3100 .iter()
3101 .find_map(|qualifier| rest.strip_prefix(qualifier))
3102 {
3103 rest = after.trim_start();
3104 } else {
3105 break;
3106 }
3107 }
3108 rest.strip_prefix("fn ")?.split(['(', '<']).next()
3109 }
3110
3111 #[test]
3115 fn test_push_path_claims_an_unclaimed_package_instead_of_stranding_it() {
3116 let manager = create_test_manager("alice");
3117 let legacy = manager.get_or_create_key_package().unwrap();
3118 assert!(legacy.assigned_peer.is_none());
3119
3120 let taken = manager.take_push_key_package(&addr("bob")).unwrap();
3121 assert_eq!(
3122 taken.bundle.package_id, legacy.package_id,
3123 "the pre-existing package was stranded instead of claimed"
3124 );
3125 assert_eq!(
3126 taken.bundle.assigned_peer.as_deref(),
3127 Some(addr("bob").as_str())
3128 );
3129 assert_eq!(manager.count_valid_key_packages().unwrap(), 1);
3130 }
3131
3132 #[test]
3137 fn test_push_pool_ceiling_shares_a_package_and_reports_it() {
3138 let manager = create_test_manager("alice");
3139
3140 for i in 0..MAX_PUSH_KEY_PACKAGES {
3141 let taken = manager.take_push_key_package(&format!("peer{i}")).unwrap();
3142 assert!(
3143 !taken.pool_exhausted,
3144 "pool reported exhausted at {i}, below the ceiling"
3145 );
3146 }
3147 assert_eq!(
3148 manager.count_valid_key_packages().unwrap(),
3149 MAX_PUSH_KEY_PACKAGES
3150 );
3151
3152 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
3153 assert!(over.pool_exhausted, "ceiling breach was not reported");
3154 assert_eq!(
3155 manager.count_valid_key_packages().unwrap(),
3156 MAX_PUSH_KEY_PACKAGES,
3157 "the pool grew past its ceiling"
3158 );
3159
3160 let established = manager.take_push_key_package("peer0").unwrap();
3163 assert!(!established.pool_exhausted);
3164 assert_eq!(established.bundle.assigned_peer.as_deref(), Some("peer0"));
3165 }
3166
3167 #[test]
3173 fn test_ceiling_claims_an_unclaimed_package_rather_than_sharing() {
3174 let manager = create_test_manager("alice");
3175
3176 for i in 0..(MAX_PUSH_KEY_PACKAGES - 1) {
3177 manager.take_push_key_package(&format!("peer{i}")).unwrap();
3178 }
3179 let unclaimed = manager.get_or_create_key_package().unwrap();
3182 assert_eq!(
3183 manager.count_valid_key_packages().unwrap(),
3184 MAX_PUSH_KEY_PACKAGES,
3185 "test premise: the pool is at the ceiling"
3186 );
3187
3188 let taken = manager.take_push_key_package("late-arrival").unwrap();
3189 assert!(
3190 !taken.pool_exhausted,
3191 "shared a package while a claimable one was in the pool"
3192 );
3193 assert_eq!(
3194 taken.bundle.package_id, unclaimed.package_id,
3195 "the claimable package was passed over"
3196 );
3197 assert_eq!(taken.bundle.assigned_peer.as_deref(), Some("late-arrival"));
3198 assert_eq!(
3199 manager.count_valid_key_packages().unwrap(),
3200 MAX_PUSH_KEY_PACKAGES,
3201 "claiming grew the pool"
3202 );
3203
3204 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
3207 assert!(over.pool_exhausted);
3208 }
3209
3210 #[test]
3211 fn test_import_key_package_binds_credential_identity() {
3212 let alice = create_test_manager("alice");
3213 let mallory = create_test_manager("mallory");
3214 let mallory_kp = mallory.generate_key_package().unwrap();
3215
3216 let err = alice
3219 .import_key_package(&addr("bob"), &mallory_kp.key_package_data)
3220 .unwrap_err();
3221 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3222
3223 alice
3225 .import_key_package(&addr("mallory"), &mallory_kp.key_package_data)
3226 .unwrap();
3227 }
3228
3229 fn substituted_key_package(claimed: &str, keys: &SignatureKeyPair) -> Vec<u8> {
3239 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3240 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
3241 keys.store(provider.storage()).unwrap();
3242
3243 let credential = CredentialWithKey {
3244 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
3245 signature_key: keys.public().into(),
3246 };
3247
3248 KeyPackage::builder()
3249 .build(DEFAULT_CIPHERSUITE, &provider, keys, credential)
3250 .unwrap()
3251 .key_package()
3252 .tls_serialize_detached()
3253 .unwrap()
3254 }
3255
3256 #[test]
3257 fn test_credential_identity_check_alone_does_not_stop_a_substitution() {
3258 let (mallorys_keys, _) = test_identity("mallory");
3263 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3264
3265 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3266 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
3267 let key_package = KeyPackageIn::tls_deserialize_exact(&forged)
3268 .unwrap()
3269 .validate(provider.crypto(), ProtocolVersion::Mls10)
3270 .unwrap();
3271
3272 MlsManager::verify_credential_identity(&key_package, &addr("bob"))
3273 .expect("the identity check cannot tell the two bobs apart");
3274 }
3275
3276 #[test]
3277 fn test_substituted_key_package_is_refused_by_the_derivation() {
3278 let alice = create_test_manager("alice");
3283 let real_bob = create_test_manager("bob");
3284 let (mallorys_keys, _) = test_identity("mallory");
3285
3286 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3287 let err = alice.import_key_package(&addr("bob"), &forged).unwrap_err();
3288 assert!(
3289 matches!(
3290 err,
3291 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
3292 ),
3293 "expected an address mismatch, got {:?}",
3294 err
3295 );
3296
3297 let real_kp = real_bob.generate_key_package().unwrap();
3299 alice
3300 .import_key_package(&addr("bob"), &real_kp.key_package_data)
3301 .expect("the real peer's key package derives to its own address");
3302 }
3303
3304 #[test]
3305 fn test_a_nickname_peer_id_is_refused_rather_than_skipping_the_check() {
3306 let alice = create_test_manager("alice");
3310 let bob_kp = create_test_manager("bob").generate_key_package().unwrap();
3311
3312 let err = alice
3313 .import_key_package("bob", &bob_kp.key_package_data)
3314 .unwrap_err();
3315 assert!(
3316 matches!(
3317 err,
3318 MlsError::CredentialIdentityMismatch { .. } | MlsError::InvalidUserId(_)
3319 ),
3320 "a nickname must be refused, got {:?}",
3321 err
3322 );
3323 }
3324
3325 #[test]
3326 fn test_add_group_member_binds_identity_and_derivation() {
3327 let alice = create_test_manager("alice");
3331 let bob = create_test_manager("bob");
3332 let mallory = create_test_manager("mallory");
3333
3334 let group_id = alice.create_group("Test Group").unwrap().group_id;
3335
3336 let mallory_kp = mallory.generate_key_package().unwrap();
3338 let err = alice
3339 .add_group_member(&group_id, &addr("bob"), &mallory_kp.key_package_data)
3340 .unwrap_err();
3341 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3342
3343 let (mallorys_keys, _) = test_identity("mallory");
3345 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3346 let err = alice
3347 .add_group_member(&group_id, &addr("bob"), &forged)
3348 .unwrap_err();
3349 assert!(matches!(err, MlsError::KeyPackageAddressMismatch { .. }));
3350
3351 let bob_kp = bob.generate_key_package().unwrap();
3353 alice
3354 .add_group_member(&group_id, &addr("bob"), &bob_kp.key_package_data)
3355 .expect("the real invitee is admitted");
3356 }
3357
3358 #[test]
3359 fn test_import_key_package_rejects_hostile_user_id() {
3360 let alice = create_test_manager("alice");
3361 let bob = create_test_manager("bob");
3362 let bob_kp = bob.generate_key_package().unwrap();
3363
3364 for hostile in ["", "..", "bob/evil", "bob\\evil", "bob\0evil"] {
3365 let err = alice
3366 .import_key_package(hostile, &bob_kp.key_package_data)
3367 .unwrap_err();
3368 assert!(
3369 matches!(err, MlsError::InvalidUserId(_)),
3370 "expected InvalidUserId for {:?}, got {:?}",
3371 hostile,
3372 err
3373 );
3374 }
3375 }
3376
3377 #[test]
3378 fn test_get_contact_key_package_rejects_poisoned_store() {
3379 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3383 let (alice_keys, alice_address) = test_identity("alice");
3384 seed_identity(&storage, &alice_keys);
3385 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
3386 let mallory = create_test_manager("mallory");
3387 let mallory_kp = mallory.generate_key_package().unwrap();
3388
3389 storage
3390 .store(
3391 StorageKeyType::ContactKeyPackage.as_str(),
3392 &addr("bob"),
3393 &mallory_kp.key_package_data,
3394 )
3395 .unwrap();
3396
3397 let err = alice.create_session(&addr("bob")).unwrap_err();
3399 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3400 }
3401
3402 #[test]
3421 fn test_get_contact_key_package_rejects_a_substituted_package() {
3422 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3423 let (alice_keys, alice_address) = test_identity("alice");
3424 seed_identity(&storage, &alice_keys);
3425 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
3426
3427 let (mallorys_keys, _) = test_identity("mallory");
3429 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3430
3431 storage
3432 .store(
3433 StorageKeyType::ContactKeyPackage.as_str(),
3434 &addr("bob"),
3435 &forged,
3436 )
3437 .unwrap();
3438
3439 let err = alice.create_session(&addr("bob")).unwrap_err();
3440 assert!(
3441 matches!(
3442 err,
3443 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
3444 ),
3445 "a substituted cached package must be refused at use time, got {:?}",
3446 err
3447 );
3448
3449 assert!(!alice.has_session(&addr("bob")).unwrap());
3451 }
3452
3453 #[test]
3454 fn test_replace_session_with_welcome_rejects_hostile_inviter_id() {
3455 let alice = create_test_manager("alice");
3459 let welcome = WelcomeMessage {
3460 group_id: slot("alice", "bob"),
3461 welcome_data: vec![],
3462 inviter_id: "../../etc".to_string(),
3463 group_name: None,
3464 timestamp_ms: 0,
3465 };
3466 let err = alice.replace_session_with_welcome(&welcome).unwrap_err();
3467 assert!(matches!(err, MlsError::InvalidUserId(_)));
3468 }
3469
3470 fn create_test_session() -> (MlsManager, MlsManager) {
3472 let alice = create_test_manager("alice");
3473 let bob = create_test_manager("bob");
3474 let bob_kp = bob.generate_key_package().unwrap();
3475 alice
3476 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3477 .unwrap();
3478 let welcome = alice.create_session(&addr("bob")).unwrap();
3479 bob.join_session(&welcome).unwrap();
3480 (alice, bob)
3481 }
3482
3483 use crate::storage::{StorageError, StorageResult};
3484
3485 struct StampDeleteFails(InMemoryStorage);
3487
3488 impl MlsStorage for StampDeleteFails {
3489 fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> StorageResult<()> {
3490 self.0.store(key_type, key_id, data)
3491 }
3492 fn load(&self, key_type: &str, key_id: &str) -> StorageResult<Option<Vec<u8>>> {
3493 self.0.load(key_type, key_id)
3494 }
3495 fn delete(&self, key_type: &str, key_id: &str) -> StorageResult<()> {
3496 if key_type == StorageKeyType::GroupMetadata.as_str() {
3497 return Err(StorageError::DeleteFailed("refused".to_string()));
3498 }
3499 self.0.delete(key_type, key_id)
3500 }
3501 fn list_keys(&self, key_type: &str) -> StorageResult<Vec<String>> {
3502 self.0.list_keys(key_type)
3503 }
3504 }
3505
3506 #[test]
3510 fn a_failed_delete_session_leaves_the_session_in_place() {
3511 let storage: Arc<dyn MlsStorage> = Arc::new(StampDeleteFails(InMemoryStorage::new()));
3512 let (keys, address) = test_identity("alice");
3513 seed_identity(&storage, &keys);
3514 let alice = MlsManager::new(address.to_string(), storage).unwrap();
3515 let bob = create_test_manager("bob");
3516 let bob_kp = bob.generate_key_package().unwrap();
3517 alice
3518 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3519 .unwrap();
3520 alice.create_session(&addr("bob")).unwrap();
3521
3522 assert!(alice.delete_session(&addr("bob")).is_err());
3523 assert!(alice.has_session(&addr("bob")).unwrap());
3524 assert!(alice
3525 .session_established_at_ms(&addr("bob"))
3526 .unwrap()
3527 .is_some());
3528 }
3529
3530 #[test]
3531 fn test_session_decrypt_rejects_spoofed_sender() {
3532 let (alice, bob) = create_test_session();
3533
3534 let ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
3540 let err = bob.decrypt_from_user(&ct, &addr("mallory")).unwrap_err();
3541 assert!(
3542 matches!(err, MlsError::SessionIdentityMismatch { .. }),
3543 "spoofed sender must be rejected by the slot binding, got {:?}",
3544 err
3545 );
3546
3547 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
3552 assert_eq!(pt.as_deref(), Some(&b"hello"[..]));
3553
3554 }
3558
3559 #[test]
3560 fn test_epoch_fork_classifies_as_session_desync() {
3561 let (alice, bob) = create_test_session();
3566
3567 let group_id = slot("alice", "bob");
3570 alice.update_keys(&group_id).unwrap();
3571
3572 let ct = alice.encrypt_for_user(&addr("bob"), b"after-fork").unwrap();
3575 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
3576 assert!(
3577 matches!(err, MlsError::SessionDesync(_)),
3578 "epoch fork must classify as SessionDesync, got {:?}",
3579 err
3580 );
3581 }
3582
3583 fn push_varint(out: &mut Vec<u8>, len: usize) {
3596 match len {
3597 0..=63 => out.push(len as u8),
3598 64..=16_383 => {
3599 out.push(0x40 | (len >> 8) as u8);
3600 out.push((len & 0xff) as u8);
3601 }
3602 _ => panic!("test payloads stay under the 2-byte varint ceiling"),
3603 }
3604 }
3605
3606 fn forge_private_message(group_id: &str, epoch: u64) -> Vec<u8> {
3607 let mut out = Vec::new();
3608 out.extend_from_slice(&1u16.to_be_bytes()); out.extend_from_slice(&2u16.to_be_bytes()); push_varint(&mut out, group_id.len());
3611 out.extend_from_slice(group_id.as_bytes());
3612 out.extend_from_slice(&epoch.to_be_bytes());
3613 out.push(1); out.push(0); out.push(8); out.extend_from_slice(&[0u8; 8]);
3617 out.push(16); out.extend_from_slice(&[0u8; 16]);
3619 out
3620 }
3621
3622 #[test]
3623 fn test_forged_frame_reaches_session_desync_without_any_key_material() {
3624 let (_alice, bob) = create_test_session();
3637
3638 let forged = EncryptedMessage {
3639 group_id: slot("alice", "bob"),
3640 message_type: MlsMessageType::Application,
3641 epoch: 9_999,
3642 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
3643 sender_id: addr("alice"),
3644 timestamp_ms: 0,
3645 };
3646
3647 let err = bob.decrypt_from_user(&forged, &addr("alice")).unwrap_err();
3648 assert!(
3649 matches!(err, MlsError::SessionDesync(_)),
3650 "a forged future-epoch frame reaches the desync classification pre-AEAD, got {:?}",
3651 err
3652 );
3653 }
3654
3655 #[test]
3656 fn test_forged_frame_naming_a_foreign_slot_is_rejected_before_desync() {
3657 let (_alice, bob) = create_test_session();
3663
3664 let forged = EncryptedMessage {
3665 group_id: slot("alice", "bob"),
3666 message_type: MlsMessageType::Application,
3667 epoch: 9_999,
3668 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
3669 sender_id: addr("mallory"),
3670 timestamp_ms: 0,
3671 };
3672
3673 let err = bob
3674 .decrypt_from_user(&forged, &addr("mallory"))
3675 .unwrap_err();
3676 assert!(
3677 matches!(err, MlsError::SessionIdentityMismatch { .. }),
3678 "a frame naming another pair's slot must not classify as recoverable, got {:?}",
3679 err
3680 );
3681 }
3682
3683 #[test]
3684 fn test_corrupt_ciphertext_is_not_classified_as_session_desync() {
3685 let (alice, bob) = create_test_session();
3691 let mut ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
3692 for b in ct.ciphertext.iter_mut() {
3694 *b ^= 0xFF;
3695 }
3696 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
3697 assert!(
3698 !matches!(err, MlsError::SessionDesync(_)),
3699 "corrupt ciphertext must not be treated as a recoverable desync, got {:?}",
3700 err
3701 );
3702 }
3703
3704 #[test]
3705 fn test_join_session_binds_slot_to_inviter() {
3706 let bob = create_test_manager("bob");
3713
3714 let hijack = WelcomeMessage {
3717 group_id: slot("alice", "bob"),
3718 welcome_data: vec![], inviter_id: addr("mallory"),
3720 group_name: None,
3721 timestamp_ms: 0,
3722 };
3723
3724 let err = bob.join_session(&hijack).unwrap_err();
3726 assert!(
3727 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3728 "join_session: expected WelcomeIdentityMismatch, got {:?}",
3729 err
3730 );
3731 let err = bob.replace_session_with_welcome(&hijack).unwrap_err();
3732 assert!(
3733 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3734 "replace_session_with_welcome: expected WelcomeIdentityMismatch, got {:?}",
3735 err
3736 );
3737
3738 assert!(!bob.has_session(&addr("alice")).unwrap());
3740 assert!(!bob.has_session(&addr("mallory")).unwrap());
3741
3742 let bob_kp = bob.generate_key_package().unwrap();
3746 let mallory = create_test_manager("mallory");
3747 mallory
3748 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3749 .unwrap();
3750 let legit = mallory.create_session(&addr("bob")).unwrap();
3751 bob.join_session(&legit).unwrap();
3752 assert!(bob.has_session(&addr("mallory")).unwrap());
3753 }
3754
3755 #[test]
3756 fn test_join_group_rejects_session_namespace() {
3757 let bob = create_test_manager("bob");
3765
3766 let squat = WelcomeMessage {
3767 group_id: slot("alice", "bob"),
3768 welcome_data: vec![], inviter_id: addr("mallory"),
3770 group_name: None,
3771 timestamp_ms: 0,
3772 };
3773
3774 let err = bob.join_group(&squat).unwrap_err();
3775 assert!(
3776 matches!(err, MlsError::ReservedSessionNamespace { .. }),
3777 "join_group: expected ReservedSessionNamespace, got {:?}",
3778 err
3779 );
3780
3781 assert!(!bob.has_session(&addr("alice")).unwrap());
3783
3784 let legit_ns = WelcomeMessage {
3788 group_id: GroupId::new("group:0bd6e5f2-3a70-4a4e-9c3f-1c1f2a3b4c5d").unwrap(),
3789 welcome_data: vec![],
3790 inviter_id: addr("mallory"),
3791 group_name: None,
3792 timestamp_ms: 0,
3793 };
3794 let err = bob.join_group(&legit_ns).unwrap_err();
3795 assert!(
3796 !matches!(err, MlsError::ReservedSessionNamespace { .. }),
3797 "a group:-namespaced Welcome must not trip the session-namespace guard, got {:?}",
3798 err
3799 );
3800 }
3801
3802 #[test]
3803 fn test_join_group_rejects_embedded_group_id_mismatch() {
3804 let bob = create_test_manager("bob");
3813 let mallory = create_test_manager("mallory");
3814
3815 let bob_kp = bob.generate_key_package().unwrap();
3817 let embedded = GroupId::new("group:11111111-1111-4111-8111-111111111111").unwrap();
3818 let cred = mallory.get_credential().unwrap();
3819 let signer = mallory.get_signer().unwrap();
3820 mallory
3821 .group_manager
3822 .create_group(&embedded, &cred, &signer)
3823 .unwrap();
3824 let (welcome, _commit) = mallory
3825 .add_group_member(&embedded, &addr("bob"), &bob_kp.key_package_data)
3826 .unwrap();
3827 assert_eq!(welcome.group_id, embedded);
3828
3829 let wire = GroupId::new("group:22222222-2222-4222-8222-222222222222").unwrap();
3833 let tampered = WelcomeMessage {
3834 group_id: wire.clone(),
3835 ..welcome
3836 };
3837
3838 let err = bob.join_group(&tampered).unwrap_err();
3839 assert!(
3840 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3841 "expected WelcomeGroupIdMismatch, got {:?}",
3842 err
3843 );
3844
3845 assert!(bob.group_manager.load_group(&wire).unwrap().is_none());
3847 assert!(bob.group_manager.load_group(&embedded).unwrap().is_none());
3848 }
3849
3850 #[test]
3851 fn test_welcome_embedded_id_cannot_hijack_session_slot() {
3852 let alice = create_test_manager("alice");
3861 let bob = create_test_manager("bob");
3862 let bob_kp = bob.generate_key_package().unwrap();
3863 alice
3864 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3865 .unwrap();
3866 let welcome = alice.create_session(&addr("bob")).unwrap();
3867 bob.join_session(&welcome).unwrap();
3868 assert!(bob.has_session(&addr("alice")).unwrap());
3869
3870 let mallory = create_test_manager("mallory");
3872 let bob_kp2 = bob.generate_key_package().unwrap();
3873 let squat = slot("alice", "bob");
3874 let cred = mallory.get_credential().unwrap();
3875 let signer = mallory.get_signer().unwrap();
3876 mallory
3877 .group_manager
3878 .create_group(&squat, &cred, &signer)
3879 .unwrap();
3880 let (mal_welcome, _commit) = mallory
3881 .add_group_member(&squat, &addr("bob"), &bob_kp2.key_package_data)
3882 .unwrap();
3883
3884 let attack = WelcomeMessage {
3886 group_id: slot("bob", "mallory"),
3887 welcome_data: mal_welcome.welcome_data,
3888 inviter_id: addr("mallory"),
3889 group_name: None,
3890 timestamp_ms: 0,
3891 };
3892
3893 let err = bob.join_session(&attack).unwrap_err();
3894 assert!(
3895 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3896 "expected WelcomeGroupIdMismatch, got {:?}",
3897 err
3898 );
3899
3900 assert!(bob.has_session(&addr("alice")).unwrap());
3902 let ct = bob
3903 .encrypt_for_user(&addr("alice"), b"still private")
3904 .unwrap();
3905 let pt = alice.decrypt_from_user(&ct, &addr("bob")).unwrap();
3906 assert_eq!(pt.as_deref(), Some(&b"still private"[..]));
3907 }
3908
3909 fn create_test_group_with_bob() -> (MlsManager, MlsManager, GroupId) {
3912 let alice = create_test_manager("alice");
3913 let bob = create_test_manager("bob");
3914 let bob_kp = bob.generate_key_package().unwrap();
3915 alice
3916 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3917 .unwrap();
3918 let info = alice.create_group("Test Group").unwrap();
3919 let gid = info.group_id.clone();
3920 let (welcome, _commit) = alice
3921 .add_group_member(&gid, &addr("bob"), &bob_kp.key_package_data)
3922 .unwrap();
3923 bob.join_group(&welcome).unwrap();
3924 (alice, bob, gid)
3925 }
3926
3927 #[test]
3928 fn test_group_decrypt_rejects_spoofed_sender() {
3929 let (alice, bob, gid) = create_test_group_with_bob();
3930
3931 let ct = alice.encrypt_for_group(&gid, b"group message").unwrap();
3932 let err = bob.decrypt_from_group(&ct, &addr("mallory")).unwrap_err();
3933 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3934
3935 let ct2 = alice.encrypt_for_group(&gid, b"another one").unwrap();
3936 let pt = bob.decrypt_from_group(&ct2, &addr("alice")).unwrap();
3937 assert_eq!(pt.as_deref(), Some(&b"another one"[..]));
3938 }
3939
3940 #[test]
3941 fn test_group_commit_with_spoofed_sender_rejected_before_merge() {
3942 let (alice, bob, gid) = create_test_group_with_bob();
3943
3944 let commit = alice.update_keys(&gid).unwrap();
3948 let epoch_before = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3949
3950 let err = bob
3951 .decrypt_from_group(&commit, &addr("mallory"))
3952 .unwrap_err();
3953 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3954
3955 let epoch_after = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3956 assert_eq!(
3957 epoch_before, epoch_after,
3958 "spoofed commit must not advance group state"
3959 );
3960 }
3961
3962 #[test]
3963 fn test_no_session_initially() {
3964 let manager = create_test_manager("alice");
3965 assert!(!manager.has_session(&addr("bob")).unwrap());
3966 assert!(manager.list_sessions().unwrap().is_empty());
3967 }
3968
3969 #[test]
3970 fn test_no_groups_initially() {
3971 let manager = create_test_manager("alice");
3972 assert!(manager.list_groups().unwrap().is_empty());
3973 }
3974
3975 #[test]
3976 fn test_group_creation() {
3977 let manager = create_test_manager("alice");
3978 let info = manager.create_group("Test Group").unwrap();
3979
3980 assert_eq!(info.name, Some("Test Group".to_string()));
3981 assert!(!info.is_session);
3982 assert_eq!(info.members.len(), 1);
3983
3984 let groups = manager.list_groups().unwrap();
3985 assert_eq!(groups.len(), 1);
3986 }
3987
3988 #[test]
3999 fn test_both_create_adopt_is_non_destructive_on_welcome_retransmit() {
4000 let alice = create_test_manager("alice");
4002 let bob = create_test_manager("bob");
4003
4004 let alice_kp = alice.generate_key_package().unwrap();
4006 let bob_kp = bob.generate_key_package().unwrap();
4007 alice
4008 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4009 .unwrap();
4010 bob.import_key_package(&addr("alice"), &alice_kp.key_package_data)
4011 .unwrap();
4012
4013 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());
4017
4018 bob.join_session(&alice_welcome).unwrap();
4021 assert!(bob.has_session(&addr("alice")).unwrap());
4022
4023 let ct = alice
4025 .encrypt_for_user(&addr("bob"), b"hello over the converged group")
4026 .unwrap();
4027 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
4028 assert_eq!(pt.as_deref(), Some(&b"hello over the converged group"[..]));
4029
4030 let retransmit = bob.join_session(&alice_welcome);
4033 assert!(
4034 retransmit.is_err(),
4035 "re-staging a consumed key package should fail"
4036 );
4037
4038 assert!(
4041 bob.has_session(&addr("alice")).unwrap(),
4042 "duplicate Welcome must not brick the converged session"
4043 );
4044
4045 let ct2 = alice
4047 .encrypt_for_user(&addr("bob"), b"still converged after retransmit")
4048 .unwrap();
4049 let pt2 = bob.decrypt_from_user(&ct2, &addr("alice")).unwrap();
4050 assert_eq!(
4051 pt2.as_deref(),
4052 Some(&b"still converged after retransmit"[..])
4053 );
4054 }
4055
4056 #[test]
4062 fn test_out_of_order_decryption_within_sender_ratchet_tolerance() {
4063 let alice = create_test_manager("alice");
4064 let bob = create_test_manager("bob");
4065
4066 let bob_kp = bob.generate_key_package().unwrap();
4067 alice
4068 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4069 .unwrap();
4070 let welcome = alice.create_session(&addr("bob")).unwrap();
4071 bob.join_session(&welcome).unwrap();
4072
4073 let ciphertexts: Vec<_> = (0..40)
4074 .map(|i| {
4075 alice
4076 .encrypt_for_user(&addr("bob"), format!("chunk {}", i).as_bytes())
4077 .unwrap()
4078 })
4079 .collect();
4080
4081 let pt = bob
4084 .decrypt_from_user(&ciphertexts[39], &addr("alice"))
4085 .unwrap();
4086 assert_eq!(pt.as_deref(), Some(&b"chunk 39"[..]));
4087
4088 let pt = bob
4091 .decrypt_from_user(&ciphertexts[10], &addr("alice"))
4092 .unwrap();
4093 assert_eq!(pt.as_deref(), Some(&b"chunk 10"[..]));
4094
4095 let res = bob.decrypt_from_user(&ciphertexts[0], &addr("alice"));
4098 assert!(
4099 !matches!(res, Ok(Some(_))),
4100 "generation beyond the tolerance must not decrypt"
4101 );
4102 }
4103
4104 #[test]
4115 fn test_a_receiver_past_the_forward_window_classifies_as_session_desync() {
4116 let (alice, bob) = create_test_session();
4117
4118 let mut last = None;
4120 for i in 0..100 {
4121 last = Some(
4122 alice
4123 .encrypt_for_user(&addr("bob"), format!("in window {i}").as_bytes())
4124 .unwrap(),
4125 );
4126 }
4127 let pt = bob
4128 .decrypt_from_user(&last.unwrap(), &addr("alice"))
4129 .unwrap();
4130 assert_eq!(pt.as_deref(), Some(&b"in window 99"[..]));
4131
4132 let gap = crate::group::SENDER_RATCHET_MAXIMUM_FORWARD_DISTANCE + 1;
4134 for _ in 0..gap {
4135 alice.encrypt_for_user(&addr("bob"), b"lost").unwrap();
4136 }
4137 let ahead = alice.encrypt_for_user(&addr("bob"), b"too far").unwrap();
4138 let err = bob.decrypt_from_user(&ahead, &addr("alice")).unwrap_err();
4139 assert!(
4140 matches!(err, MlsError::SessionDesync(_)),
4141 "a frame past the forward window must classify as SessionDesync, got {err:?}"
4142 );
4143 }
4144
4145 #[test]
4150 fn test_a_frame_past_the_out_of_order_tolerance_is_not_a_desync() {
4151 let (alice, bob) = create_test_session();
4152 let old = alice.encrypt_for_user(&addr("bob"), b"old").unwrap();
4153 let mut newest = None;
4154 for _ in 0..=crate::group::SENDER_RATCHET_OUT_OF_ORDER_TOLERANCE + 1 {
4155 newest = Some(alice.encrypt_for_user(&addr("bob"), b"newer").unwrap());
4156 }
4157 bob.decrypt_from_user(&newest.unwrap(), &addr("alice"))
4158 .unwrap();
4159 let err = bob.decrypt_from_user(&old, &addr("alice")).unwrap_err();
4160 assert!(
4161 matches!(err, MlsError::Decryption(_)),
4162 "a frame behind the tolerance must stay Decryption, got {err:?}"
4163 );
4164 }
4165
4166 #[test]
4170 fn test_unacknowledged_seals_track_seals_acks_and_session_rebuilds() {
4171 let (alice, _bob) = create_test_session();
4172 let bob = addr("bob");
4173 assert_eq!(alice.unacknowledged_seals(&bob), 0);
4174
4175 alice.encrypt_for_user(&bob, b"one").unwrap();
4176 let first = alice.last_seal_mark(&bob).unwrap();
4177 alice.encrypt_for_user(&bob, b"two").unwrap();
4178 alice.encrypt_for_user(&bob, b"three").unwrap();
4179 assert_eq!(alice.unacknowledged_seals(&bob), 3);
4180
4181 alice.note_acknowledged_seal(&bob, first);
4182 assert_eq!(alice.unacknowledged_seals(&bob), 2);
4183 alice.note_acknowledged_seal(&bob, crate::session::SealMark { index: 0, ..first });
4185 assert_eq!(alice.unacknowledged_seals(&bob), 2);
4186
4187 alice.delete_session(&bob).unwrap();
4188 assert_eq!(alice.unacknowledged_seals(&bob), 0);
4189 assert!(alice.last_seal_mark(&bob).is_none());
4190 alice.note_acknowledged_seal(&bob, crate::session::SealMark { index: 99, ..first });
4193 assert_eq!(alice.unacknowledged_seals(&bob), 0);
4194 }
4195
4196 const RFC8032_TV1_PK: [u8; 32] = [
4198 0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07,
4199 0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07,
4200 0x51, 0x1a,
4201 ];
4202
4203 const RFC8032_TV1_ADDRESS: &str = "off1qysluvwl5922yctzd0u9gpr06gn3k7ldfvgtwgvn";
4208
4209 #[test]
4217 fn derive_address_matches_the_pinned_vector() {
4218 let address = MlsManager::derive_address(&RFC8032_TV1_PK).expect("32-byte key derives");
4219 assert_eq!(address.to_string(), RFC8032_TV1_ADDRESS);
4220 assert_eq!(
4223 RFC8032_TV1_ADDRESS.parse::<Address>().expect("parses"),
4224 address
4225 );
4226 }
4227
4228 #[test]
4229 fn derive_address_rejects_wrong_key_lengths() {
4230 for len in [0usize, 31, 33, 64] {
4231 let key = vec![0u8; len];
4232 assert!(
4233 matches!(
4234 MlsManager::derive_address(&key),
4235 Err(MlsError::InvalidPublicKey(_))
4236 ),
4237 "{len}-byte key must be rejected"
4238 );
4239 }
4240 }
4241
4242 #[test]
4243 fn deprecated_derive_agrees_with_derive_address() {
4244 #[allow(deprecated)]
4247 let legacy = MlsManager::derive_user_id_from_public_key(&RFC8032_TV1_PK);
4248 assert_eq!(legacy, RFC8032_TV1_ADDRESS);
4249 assert_eq!(
4250 legacy,
4251 MlsManager::derive_address(&RFC8032_TV1_PK)
4252 .expect("derives")
4253 .to_string()
4254 );
4255 }
4256
4257 #[test]
4258 fn load_or_create_identity_mints_once_and_is_idempotent() {
4259 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4260
4261 let (first_keys, first_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4262 let (second_keys, second_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4263
4264 assert_eq!(first_keys.public(), second_keys.public());
4266 assert_eq!(first_address, second_address);
4267 assert_eq!(
4268 MlsManager::derive_address(first_keys.public()).unwrap(),
4269 first_address,
4270 "the returned address must be the stored key's derivation"
4271 );
4272 }
4273
4274 #[test]
4275 fn load_or_create_identity_adopts_an_existing_key() {
4276 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4277 let (keys, address) = test_identity("alice");
4278 seed_identity(&storage, &keys);
4279
4280 let (loaded, loaded_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4281
4282 assert_eq!(loaded.public(), keys.public());
4283 assert_eq!(loaded_address, address);
4284 }
4285
4286 #[test]
4287 fn manager_bootstrapped_at_its_derived_address_loads_that_identity() {
4288 let (manager, address) = create_addressed_manager("alice");
4289
4290 assert_eq!(manager.user_id(), address.to_string());
4291 assert_eq!(
4292 MlsManager::derive_address(&manager.get_identity_public_key().unwrap()).unwrap(),
4293 address,
4294 "the manager's identity key must derive to the address it claims"
4295 );
4296 }
4297
4298 #[test]
4303 fn manager_refuses_an_address_whose_key_is_not_the_stored_one() {
4304 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4305 let (alice_keys, alice_address) = test_identity("alice");
4306 let (_, bob_address) = test_identity("bob");
4307 seed_identity(&storage, &alice_keys);
4308
4309 let Err(err) = MlsManager::new(bob_address.to_string(), storage) else {
4310 panic!("a manager must not claim an address it holds no key for");
4311 };
4312
4313 match err {
4314 MlsError::IdentityAddressMismatch { expected, derived } => {
4315 assert_eq!(expected, bob_address.to_string());
4316 assert_eq!(derived, alice_address.to_string());
4317 }
4318 other => panic!("expected IdentityAddressMismatch, got {other:?}"),
4319 }
4320 }
4321
4322 #[test]
4326 fn manager_refuses_to_claim_an_address_with_no_stored_identity() {
4327 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4328 let (_, address) = test_identity("alice");
4329
4330 let Err(err) = MlsManager::new(address.to_string(), storage.clone()) else {
4331 panic!("a manager must not mint a key and claim a different address");
4332 };
4333 assert!(matches!(err, MlsError::IdentityAddressMismatch { .. }));
4334
4335 assert!(
4336 storage
4337 .load(StorageKeyType::Identity.as_str(), "key_pair")
4338 .unwrap()
4339 .is_none(),
4340 "a refused bootstrap must not leave a key behind"
4341 );
4342 }
4343
4344 #[test]
4356 fn manager_with_a_non_address_id_skips_the_binding_check() {
4357 let manager = MlsManager::new("alice", Arc::new(InMemoryStorage::new())).unwrap();
4358 assert_eq!(manager.user_id(), "alice");
4359 }
4360
4361 struct Impostor {
4374 provider: MlsProvider,
4375 keys: SignatureKeyPair,
4376 key_package: Vec<u8>,
4377 group: Option<MlsGroup>,
4378 }
4379
4380 impl Impostor {
4381 fn claiming(claimed: &str, key_label: &str) -> Self {
4384 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4385 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
4386 let (keys, _) = test_identity(key_label);
4387 keys.store(provider.storage()).unwrap();
4388
4389 let credential = CredentialWithKey {
4390 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
4391 signature_key: keys.public().into(),
4392 };
4393 let key_package = KeyPackage::builder()
4394 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
4395 .unwrap()
4396 .key_package()
4397 .tls_serialize_detached()
4398 .unwrap();
4399
4400 Self {
4401 provider,
4402 keys,
4403 key_package,
4404 group: None,
4405 }
4406 }
4407
4408 fn join(&mut self, welcome: &MlsMessageOut) {
4410 let bytes = welcome.tls_serialize_detached().unwrap();
4411 let welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
4412 .unwrap()
4413 .extract()
4414 {
4415 MlsMessageBodyIn::Welcome(w) => w,
4416 _ => panic!("not a welcome"),
4417 };
4418 let cfg = MlsGroupJoinConfig::builder()
4419 .use_ratchet_tree_extension(true)
4420 .build();
4421 self.group = Some(
4422 StagedWelcome::new_from_welcome(&self.provider, &cfg, welcome, None)
4423 .unwrap()
4424 .into_group(&self.provider)
4425 .unwrap(),
4426 );
4427 }
4428
4429 fn merge_commit(&mut self, commit_bytes: &[u8]) {
4435 let group = self.group.as_mut().expect("join first");
4436 let message = MlsMessageIn::tls_deserialize_exact(commit_bytes)
4437 .unwrap()
4438 .try_into_protocol_message()
4439 .unwrap();
4440 let processed = group.process_message(&self.provider, message).unwrap();
4441 match processed.into_content() {
4442 ProcessedMessageContent::StagedCommitMessage(staged) => {
4443 group.merge_staged_commit(&self.provider, *staged).unwrap();
4444 }
4445 _ => panic!("not a commit"),
4446 }
4447 }
4448
4449 fn encrypt(&mut self, plaintext: &[u8]) -> Vec<u8> {
4451 self.group
4452 .as_mut()
4453 .expect("join first")
4454 .create_message(&self.provider, &self.keys, plaintext)
4455 .unwrap()
4456 .tls_serialize_detached()
4457 .unwrap()
4458 }
4459 }
4460
4461 fn add_member_bypassing_checks(
4469 manager: &MlsManager,
4470 group_id: &GroupId,
4471 key_package_bytes: &[u8],
4472 ) -> (MlsMessageOut, MlsMessageOut) {
4473 let mut group = manager
4474 .group_manager
4475 .load_group(group_id)
4476 .unwrap()
4477 .expect("group exists");
4478 let key_package = KeyPackageIn::tls_deserialize_exact(key_package_bytes)
4479 .unwrap()
4480 .validate(
4481 manager.group_manager.provider().crypto(),
4482 ProtocolVersion::Mls10,
4483 )
4484 .unwrap();
4485 let signer = manager.get_signer().unwrap();
4486 let (commit, welcome) = manager
4487 .group_manager
4488 .add_member(&mut group, key_package, &signer)
4489 .unwrap();
4490 manager.group_manager.save_group(group_id, &group).unwrap();
4491 (commit, welcome)
4492 }
4493
4494 fn commit_message(
4495 group_id: &GroupId,
4496 commit: &MlsMessageOut,
4497 sender: &str,
4498 ) -> EncryptedMessage {
4499 EncryptedMessage {
4500 group_id: group_id.clone(),
4501 message_type: MlsMessageType::Commit,
4502 epoch: 0,
4503 ciphertext: commit.tls_serialize_detached().unwrap(),
4504 sender_id: sender.to_string(),
4505 timestamp_ms: 0,
4506 }
4507 }
4508
4509 fn app_message(group_id: &GroupId, ciphertext: Vec<u8>, sender: &str) -> EncryptedMessage {
4510 EncryptedMessage {
4511 group_id: group_id.clone(),
4512 message_type: MlsMessageType::Application,
4513 epoch: 0,
4514 ciphertext,
4515 sender_id: sender.to_string(),
4516 timestamp_ms: 0,
4517 }
4518 }
4519
4520 #[test]
4528 fn test_committed_forged_leaf_is_refused_and_cannot_speak_as_its_victim() {
4529 let mallory = create_test_manager("mallory");
4530 let alice = create_test_manager("alice");
4531 let alice_kp = alice.generate_key_package().unwrap();
4532
4533 let gid = mallory.create_group("Team").unwrap().group_id;
4534 let (welcome, _) = mallory
4535 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4536 .unwrap();
4537 alice.join_group(&welcome).unwrap();
4538 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
4539
4540 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4542 assert!(matches!(
4543 mallory
4544 .add_group_member(&gid, &addr("bob"), &impostor.key_package)
4545 .unwrap_err(),
4546 MlsError::KeyPackageAddressMismatch { .. }
4547 ));
4548 let (commit, forged_welcome) =
4549 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4550
4551 let err = alice
4553 .decrypt_from_group(
4554 &commit_message(&gid, &commit, &addr("mallory")),
4555 &addr("mallory"),
4556 )
4557 .unwrap_err();
4558 assert!(
4559 matches!(
4560 err,
4561 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4562 if *claimed == addr("bob") && *context == crate::error::LeafSource::CommitAdd
4563 ),
4564 "expected a CommitAdd binding refusal, got {err:?}"
4565 );
4566
4567 let after = alice.get_group_info(&gid).unwrap().unwrap();
4569 assert_eq!(
4570 after.epoch, epoch_before,
4571 "a refused commit advanced the epoch"
4572 );
4573 assert!(
4574 !after.members.contains(&addr("bob")),
4575 "roster: {:?}",
4576 after.members
4577 );
4578
4579 impostor.join(&forged_welcome);
4587 let forged = impostor.encrypt(b"send me the recovery phrase");
4588 assert!(
4589 alice
4590 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
4591 .is_err(),
4592 "a forged leaf's message was delivered"
4593 );
4594 }
4595
4596 #[test]
4600 fn test_welcome_carrying_a_forged_leaf_is_refused() {
4601 let mallory = create_test_manager("mallory");
4602 let alice = create_test_manager("alice");
4603
4604 let gid = mallory.create_group("Team").unwrap().group_id;
4605 let impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4606 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4607
4608 let alice_kp = alice.generate_key_package().unwrap();
4610 let (welcome, _) = mallory
4611 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4612 .unwrap();
4613
4614 let err = alice.join_group(&welcome).unwrap_err();
4615 assert!(
4616 matches!(
4617 err,
4618 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4619 if *claimed == addr("bob") && *context == crate::error::LeafSource::WelcomeTree
4620 ),
4621 "expected a WelcomeTree binding refusal, got {err:?}"
4622 );
4623
4624 assert!(alice.get_group_info(&gid).unwrap().is_none());
4626 }
4627
4628 #[test]
4633 fn test_session_welcome_carrying_a_forged_leaf_is_refused_non_destructively() {
4634 let alice = create_test_manager("alice");
4635 let bob = create_test_manager("bob");
4636 let mallory = create_test_manager("mallory");
4637
4638 let bob_kp = bob.generate_key_package().unwrap();
4640 alice
4641 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4642 .unwrap();
4643 bob.join_session(&alice.create_session(&addr("bob")).unwrap())
4644 .unwrap();
4645 assert!(bob.has_session(&addr("alice")).unwrap());
4646
4647 let bob_kp2 = bob.generate_key_package().unwrap();
4650 let slot_id = slot("bob", "mallory");
4651 let cred = mallory.get_credential().unwrap();
4652 let signer = mallory.get_signer().unwrap();
4653 mallory
4654 .group_manager
4655 .create_group(&slot_id, &cred, &signer)
4656 .unwrap();
4657 let impostor = Impostor::claiming(&addr("carol"), "mallorys-second-key");
4658 add_member_bypassing_checks(&mallory, &slot_id, &impostor.key_package);
4659 let (welcome, _) = mallory
4660 .add_group_member(&slot_id, &addr("bob"), &bob_kp2.key_package_data)
4661 .unwrap();
4662
4663 let err = bob.join_session(&welcome).unwrap_err();
4664 assert!(
4665 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
4666 if *context == crate::error::LeafSource::WelcomeTree),
4667 "expected a WelcomeTree binding refusal, got {err:?}"
4668 );
4669
4670 assert!(bob.has_session(&addr("alice")).unwrap());
4672 let ct = bob
4673 .encrypt_for_user(&addr("alice"), b"still private")
4674 .unwrap();
4675 assert_eq!(
4676 alice
4677 .decrypt_from_user(&ct, &addr("bob"))
4678 .unwrap()
4679 .as_deref(),
4680 Some(&b"still private"[..])
4681 );
4682 }
4683
4684 #[test]
4692 fn test_forged_leaf_seated_behind_the_gates_still_cannot_speak() {
4693 let mallory = create_test_manager("mallory");
4694 let alice = create_test_manager("alice");
4695
4696 let gid = mallory.create_group("Team").unwrap().group_id;
4697 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4698 let (_, forged_welcome) =
4699 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4700 impostor.join(&forged_welcome);
4701
4702 let alice_kp = alice.generate_key_package().unwrap();
4703 let (welcome, add_alice_commit) = mallory
4704 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4705 .unwrap();
4706 impostor.merge_commit(&add_alice_commit.ciphertext);
4709
4710 let bytes = welcome.welcome_data.clone();
4713 let staged_welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
4714 .unwrap()
4715 .extract()
4716 {
4717 MlsMessageBodyIn::Welcome(w) => w,
4718 _ => panic!("not a welcome"),
4719 };
4720 let cfg = MlsGroupJoinConfig::builder()
4721 .use_ratchet_tree_extension(true)
4722 .build();
4723 let group = StagedWelcome::new_from_welcome(&alice.provider, &cfg, staged_welcome, None)
4724 .unwrap()
4725 .into_group(&alice.provider)
4726 .unwrap();
4727 alice.group_manager.save_group(&gid, &group).unwrap();
4728
4729 assert!(
4731 group
4732 .members()
4733 .any(|m| m.credential.serialized_content() == addr("bob").as_bytes()),
4734 "test premise: the forged leaf must be installed"
4735 );
4736
4737 let forged = impostor.encrypt(b"send me the recovery phrase");
4739 let err = alice
4740 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
4741 .unwrap_err();
4742 assert!(
4743 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
4744 if *context == crate::error::LeafSource::MessageSender),
4745 "expected a MessageSender binding refusal, got {err:?}"
4746 );
4747
4748 let info = alice.get_group_info(&gid).unwrap().unwrap();
4751 assert!(
4752 !info.members.contains(&addr("bob")),
4753 "roster surfaced an unproven member: {:?}",
4754 info.members
4755 );
4756 assert!(
4757 info.members.contains(&addr("mallory")),
4758 "{:?}",
4759 info.members
4760 );
4761 }
4762
4763 #[test]
4777 fn test_member_renaming_their_own_leaf_to_a_peers_address_is_refused() {
4778 let (alice, bob, gid) = create_test_group_with_bob();
4782 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
4783
4784 let mut group = bob.group_manager.load_group(&gid).unwrap().unwrap();
4785 let signer = bob.get_signer().unwrap();
4786 let forged = CredentialWithKey {
4787 credential: Credential::new(CredentialType::Basic, addr("alice").as_bytes().to_vec()),
4790 signature_key: signer.public().into(),
4791 };
4792 let params = LeafNodeParameters::builder()
4793 .with_credential_with_key(forged)
4794 .build();
4795 let bundle = group
4796 .self_update(&bob.provider, &signer, params)
4797 .expect("MLS permits a credential change; the AS is what refuses it");
4798 group.merge_pending_commit(&bob.provider).unwrap();
4799 bob.group_manager.save_group(&gid, &group).unwrap();
4800 let (commit, _, _) = bundle.into_contents();
4801
4802 let err = alice
4803 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4804 .unwrap_err();
4805 assert!(
4806 matches!(
4807 err,
4808 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4809 if *claimed == addr("alice") && *context == crate::error::LeafSource::CommitPath
4810 ),
4811 "expected a CommitPath binding refusal, got {err:?}"
4812 );
4813
4814 let after = alice.get_group_info(&gid).unwrap().unwrap();
4817 assert_eq!(after.epoch, epoch_before);
4818 assert!(after.members.contains(&addr("bob")), "{:?}", after.members);
4819 assert_eq!(
4820 after
4821 .members
4822 .iter()
4823 .filter(|m| **m == addr("alice"))
4824 .count(),
4825 1,
4826 "alice must appear once, as herself: {:?}",
4827 after.members
4828 );
4829 }
4830
4831 #[test]
4834 fn test_leaf_with_a_nickname_credential_is_refused() {
4835 let mallory = create_test_manager("mallory");
4836 let alice = create_test_manager("alice");
4837 let alice_kp = alice.generate_key_package().unwrap();
4838
4839 let gid = mallory.create_group("Team").unwrap().group_id;
4840 let impostor = Impostor::claiming("bob", "mallorys-second-key");
4841 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4842 let (welcome, _) = mallory
4843 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4844 .unwrap();
4845
4846 let err = alice.join_group(&welcome).unwrap_err();
4847 assert!(
4848 matches!(err, MlsError::LeafAddressMismatch { ref claimed, .. } if claimed == "bob"),
4849 "a nickname credential was accepted or misreported: {err:?}"
4850 );
4851 }
4852
4853 #[test]
4861 fn test_key_rotation_and_ordinary_traffic_still_pass_the_binding() {
4862 let (alice, bob, gid) = create_test_group_with_bob();
4863
4864 let commit = alice.update_keys(&gid).unwrap();
4865 bob.decrypt_from_group(&commit, &addr("alice"))
4866 .expect("an honest key rotation must still merge");
4867
4868 let ct = alice.encrypt_for_group(&gid, b"after rotation").unwrap();
4869 assert_eq!(
4870 bob.decrypt_from_group(&ct, &addr("alice"))
4871 .unwrap()
4872 .as_deref(),
4873 Some(&b"after rotation"[..])
4874 );
4875
4876 assert!(bob
4878 .get_group_info(&gid)
4879 .unwrap()
4880 .unwrap()
4881 .members
4882 .contains(&addr("bob")));
4883 }
4884
4885 #[test]
4893 fn test_welcome_whose_group_context_has_external_senders_is_refused() {
4894 let mallory = create_test_manager("mallory");
4895 let alice = create_test_manager("alice");
4896
4897 let gid = mallory.create_group("Team").unwrap().group_id;
4898
4899 {
4901 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
4902 let signer = mallory.get_signer().unwrap();
4903 let outsider =
4904 SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
4905 let external = ExternalSender::new(
4906 outsider.public().into(),
4907 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
4908 );
4909 group
4910 .commit_builder()
4911 .propose_group_context_extensions(Extensions::single(Extension::ExternalSenders(
4912 vec![external],
4913 )))
4914 .load_psks(mallory.provider.storage())
4915 .unwrap()
4916 .build(
4917 mallory.provider.rand(),
4918 mallory.provider.crypto(),
4919 &signer,
4920 |_| true,
4921 )
4922 .unwrap()
4923 .stage_commit(&mallory.provider)
4924 .unwrap();
4925 group.merge_pending_commit(&mallory.provider).unwrap();
4926 mallory.group_manager.save_group(&gid, &group).unwrap();
4927 }
4928
4929 let alice_kp = alice.generate_key_package().unwrap();
4930 let (welcome, _) = mallory
4931 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4932 .unwrap();
4933
4934 let err = alice.join_group(&welcome).unwrap_err();
4935 assert!(
4936 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4937 if detail.contains("ExternalSenders")),
4938 "expected the invite to be declined for its ExternalSenders context, got {err:?}"
4939 );
4940 assert!(
4941 alice.get_group_info(&gid).unwrap().is_none(),
4942 "a declined Welcome must not install the group"
4943 );
4944 }
4945
4946 #[test]
4955 fn test_commit_proposing_external_senders_is_refused() {
4956 let (alice, mallory, gid) = create_test_group_with_bob();
4957
4958 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
4959 let signer = mallory.get_signer().unwrap();
4960
4961 let outsider = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
4963 let external = ExternalSender::new(
4964 outsider.public().into(),
4965 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
4966 );
4967 let extensions = Extensions::single(Extension::ExternalSenders(vec![external]));
4968
4969 let bundle = group
4970 .commit_builder()
4971 .propose_group_context_extensions(extensions)
4972 .load_psks(mallory.provider.storage())
4973 .unwrap()
4974 .build(
4975 mallory.provider.rand(),
4976 mallory.provider.crypto(),
4977 &signer,
4978 |_| true,
4979 )
4980 .unwrap()
4981 .stage_commit(&mallory.provider)
4982 .unwrap();
4983 let (commit, _, _) = bundle.into_contents();
4984
4985 let err = alice
4986 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4987 .unwrap_err();
4988 assert!(
4989 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
4990 if detail.contains("ExternalSenders")),
4991 "expected an ExternalSenders refusal, got {err:?}"
4992 );
4993 }
4994
4995 #[test]
5005 fn test_one_identity_cannot_hold_two_leaves_in_a_group() {
5006 let alice = create_test_manager("alice");
5007 let bob = create_test_manager("bob");
5008
5009 let gid = alice.create_group("Team").unwrap().group_id;
5010 let first = bob.generate_key_package().unwrap();
5011 alice
5012 .add_group_member(&gid, &addr("bob"), &first.key_package_data)
5013 .unwrap();
5014
5015 let second = bob.generate_key_package().unwrap();
5018 assert_ne!(first.package_id, second.package_id);
5019 let err = alice
5020 .add_group_member(&gid, &addr("bob"), &second.key_package_data)
5021 .unwrap_err();
5022 assert!(
5023 matches!(err, MlsError::AddMember(ref m) if m.contains("Duplicate signature key")),
5024 "expected MLS to refuse a duplicate signature key, got {err:?}"
5025 );
5026
5027 alice.remove_group_member(&gid, &addr("bob")).unwrap();
5029 assert!(!alice
5030 .get_group_info(&gid)
5031 .unwrap()
5032 .unwrap()
5033 .members
5034 .contains(&addr("bob")));
5035 }
5036
5037 #[test]
5053 fn test_external_join_proposal_is_refused_as_an_unsupported_sender() {
5054 let (alice, _bob, gid) = create_test_group_with_bob();
5055
5056 let outsider = create_test_manager("mallory");
5060 let kp_bytes = outsider.generate_key_package().unwrap().key_package_data;
5061 let key_package = KeyPackageIn::tls_deserialize_exact(&kp_bytes)
5062 .unwrap()
5063 .validate(alice.provider.crypto(), ProtocolVersion::Mls10)
5064 .unwrap();
5065 let signer = outsider.get_signer().unwrap();
5066
5067 let (mls_group_id, epoch) = {
5068 let group = alice.group_manager.load_group(&gid).unwrap().unwrap();
5069 (group.group_id().clone(), group.epoch())
5070 };
5071 let proposal =
5072 JoinProposal::new::<MlsStorageAdapter>(key_package, mls_group_id, epoch, &signer)
5073 .expect(
5074 "an external join proposal is well-formed; the SDK just declines to honour it",
5075 );
5076
5077 let err = alice
5078 .decrypt_from_group(
5079 &app_message(
5080 &gid,
5081 proposal.tls_serialize_detached().unwrap(),
5082 &addr("mallory"),
5083 ),
5084 &addr("mallory"),
5085 )
5086 .unwrap_err();
5087 assert!(
5088 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
5089 if detail.contains("not a group member")),
5090 "expected an UnsupportedSender refusal, got {err:?}"
5091 );
5092 }
5093
5094 #[test]
5108 fn test_roster_read_counts_the_leaves_it_skips() {
5109 let (alice, _bob, gid) = create_test_group_with_bob();
5110
5111 let before = alice.get_group_info(&gid).unwrap().unwrap();
5112 assert_eq!(
5113 before.unproven_members, 0,
5114 "an honest group must report no unproven members: {:?}",
5115 before.members
5116 );
5117 let honest_members = before.members.len();
5118
5119 let impostor = Impostor::claiming(&addr("carol"), "an-attackers-key");
5122 add_member_bypassing_checks(&alice, &gid, &impostor.key_package);
5123
5124 let after = alice.get_group_info(&gid).unwrap().unwrap();
5125 assert_eq!(
5126 after.unproven_members, 1,
5127 "the skipped leaf must be counted, not only dropped"
5128 );
5129 assert_eq!(
5130 after.members.len(),
5131 honest_members,
5132 "the roster must be unchanged: {:?}",
5133 after.members
5134 );
5135 assert!(
5136 !after.members.contains(&addr("carol")),
5137 "roster surfaced the unproven member: {:?}",
5138 after.members
5139 );
5140 }
5141
5142 fn key_package_with_window(label: &str, lifetime: Lifetime) -> (String, Vec<u8>) {
5152 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
5153 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
5154 let (keys, address) = test_identity(label);
5155 keys.store(provider.storage()).unwrap();
5156
5157 let credential = CredentialWithKey {
5158 credential: Credential::new(
5159 CredentialType::Basic,
5160 address.to_string().as_bytes().to_vec(),
5161 ),
5162 signature_key: keys.public().into(),
5163 };
5164 let bytes = KeyPackage::builder()
5165 .key_package_lifetime(lifetime)
5166 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
5167 .unwrap()
5168 .key_package()
5169 .tls_serialize_detached()
5170 .unwrap();
5171
5172 (address.to_string(), bytes)
5173 }
5174
5175 fn now_secs() -> u64 {
5176 std::time::SystemTime::now()
5177 .duration_since(std::time::UNIX_EPOCH)
5178 .unwrap()
5179 .as_secs()
5180 }
5181
5182 #[test]
5188 fn a_window_that_has_not_started_by_our_clock_is_reported_as_such() {
5189 let (manager, _) = create_addressed_manager("alice");
5190 let year = 365 * 24 * 60 * 60;
5191 let (peer, bytes) = key_package_with_window(
5192 "bob",
5193 Lifetime::init(
5194 now_secs() + year - 3600,
5195 now_secs() + year + 30 * 24 * 60 * 60,
5196 ),
5197 );
5198
5199 let err = manager
5200 .import_key_package(&peer, &bytes)
5201 .expect_err("a window that starts in a year is not valid now");
5202 assert!(
5203 matches!(
5204 err,
5205 MlsError::KeyPackageOutsideValidityWindow { expired: false }
5206 ),
5207 "refused for the wrong reason: {err:?}"
5208 );
5209 }
5210
5211 #[test]
5214 fn a_window_that_closed_by_our_clock_is_reported_as_such() {
5215 let (manager, _) = create_addressed_manager("alice");
5216 let day = 24 * 60 * 60;
5217 let (peer, bytes) = key_package_with_window(
5218 "bob",
5219 Lifetime::init(now_secs() - 60 * day, now_secs() - 30 * day),
5220 );
5221
5222 let err = manager
5223 .import_key_package(&peer, &bytes)
5224 .expect_err("a window that closed a month ago is not valid now");
5225 assert!(
5226 matches!(
5227 err,
5228 MlsError::KeyPackageOutsideValidityWindow { expired: true }
5229 ),
5230 "refused for the wrong reason: {err:?}"
5231 );
5232 }
5233
5234 #[test]
5238 fn only_the_window_refusal_changes_type() {
5239 let (manager, _) = create_addressed_manager("alice");
5240 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5241 let err = manager.import_key_package(&peer, &bytes).unwrap_err();
5242 assert!(matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")));
5243
5244 let err = manager
5245 .import_key_package(&peer, b"not a key package")
5246 .unwrap_err();
5247 assert!(matches!(err, MlsError::InvalidKeyPackage(_)), "{err:?}");
5248 }
5249
5250 #[test]
5253 fn a_year_long_validity_window_is_refused_at_import() {
5254 let (manager, _) = create_addressed_manager("alice");
5255
5256 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5257
5258 let err = manager
5259 .import_key_package(&peer, &bytes)
5260 .expect_err("a year-long window 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
5267 #[test]
5273 fn a_wide_window_written_straight_into_the_store_is_refused_on_read() {
5274 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
5275 let (keys, address) = test_identity("alice");
5276 seed_identity(&storage, &keys);
5277 let manager = MlsManager::new(address.to_string(), Arc::clone(&storage)).unwrap();
5278
5279 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5280 storage
5281 .store(StorageKeyType::ContactKeyPackage.as_str(), &peer, &bytes)
5282 .unwrap();
5283
5284 let err = manager
5285 .get_contact_key_package(&peer)
5286 .expect_err("a cached year-long window must be refused too");
5287 assert!(
5288 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5289 "refused for the wrong reason: {err:?}"
5290 );
5291 }
5292
5293 #[test]
5300 fn our_own_key_package_states_the_lifetime_we_document() {
5301 let (manager, _) = create_addressed_manager("alice");
5302
5303 let bundle = manager.generate_key_package().unwrap();
5304 let key_package = KeyPackageIn::tls_deserialize_exact(bundle.key_package_data.as_slice())
5305 .unwrap()
5306 .validate(manager.provider.crypto(), ProtocolVersion::Mls10)
5307 .unwrap();
5308
5309 let window = key_package.life_time();
5310 assert_eq!(
5311 window.not_after() - window.not_before(),
5312 DEFAULT_KEY_PACKAGE_LIFETIME_SECS + LIFETIME_SKEW_MARGIN_SECS,
5313 "the window on the wire is not the lifetime this crate declares"
5314 );
5315 assert!(
5316 MlsManager::verify_lifetime_bound(&key_package).is_ok(),
5317 "this install mints a package it would refuse from a peer"
5318 );
5319 }
5320
5321 #[test]
5329 fn a_year_long_validity_window_is_refused_by_a_group_invite() {
5330 let alice = create_test_manager("alice");
5331 let group_id = alice.create_group("Test Group").unwrap().group_id;
5332
5333 let (bob, wide) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5334 let err = alice
5335 .add_group_member(&group_id, &bob, &wide)
5336 .expect_err("a year-long window must be refused by the group path too");
5337 assert!(
5338 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5339 "refused for the wrong reason: {err:?}"
5340 );
5341
5342 let bob_manager = create_test_manager("bob");
5345 let bob_kp = bob_manager.generate_key_package().unwrap();
5346 alice
5347 .add_group_member(&group_id, &bob, &bob_kp.key_package_data)
5348 .expect("a package inside the cap is admitted");
5349 }
5350
5351 #[test]
5354 fn a_window_exactly_at_the_cap_is_admitted_and_one_second_over_is_not() {
5355 let cap = MAX_ACCEPTED_KEY_PACKAGE_LIFETIME.as_secs();
5356 let (manager, _) = create_addressed_manager("alice");
5357
5358 let start = now_secs() - 10;
5361
5362 let (at_cap, bytes) = key_package_with_window("bob", Lifetime::init(start, start + cap));
5363 manager
5364 .import_key_package(&at_cap, &bytes)
5365 .expect("a window exactly at the cap is admitted");
5366
5367 let (over_cap, bytes) =
5368 key_package_with_window("carol", Lifetime::init(start, start + cap + 1));
5369 let err = manager
5370 .import_key_package(&over_cap, &bytes)
5371 .expect_err("one second over the cap must be refused");
5372 assert!(
5373 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5374 "refused for the wrong reason: {err:?}"
5375 );
5376 }
5377}