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 _: () = assert!(
56 DEFAULT_KEY_PACKAGE_LIFETIME_SECS * 1000 <= offline_protocol_sealed::CTRL_FRESHNESS_PAST_MS,
57 "a resent Welcome could fall outside the freshness window while its package is still offered"
58);
59
60const LIFETIME_SKEW_MARGIN_SECS: u64 = 60 * 60;
68
69const KEY_PACKAGE_PURGE_GRACE_SECS: u64 = 7 * 24 * 60 * 60;
79
80pub const MAX_PUSH_KEY_PACKAGES: usize = 64;
91
92#[derive(Debug, Clone)]
99pub struct PushKeyPackage {
100 pub bundle: KeyPackageBundle,
102 pub pool_exhausted: bool,
104}
105
106fn newer_than(candidate: &KeyPackageBundle, current: Option<&KeyPackageBundle>) -> bool {
109 current.is_none_or(|existing| candidate.created_at_ms > existing.created_at_ms)
110}
111
112pub struct MlsManager {
114 user_id: String,
116
117 storage: Arc<dyn MlsStorage>,
119
120 provider: MlsProvider,
122
123 credential: RwLock<Option<CredentialWithKey>>,
125
126 cached_signer: RwLock<Option<SignatureKeyPair>>,
128
129 session_manager: SessionManager,
131
132 group_manager: GroupManager,
134}
135
136impl MlsManager {
137 pub fn new(user_id: impl Into<String>, storage: Arc<dyn MlsStorage>) -> Result<Self> {
139 let user_id = user_id.into();
140
141 let adapter = MlsStorageAdapter::new(storage.clone());
142 let provider = MlsProvider::new(adapter);
143
144 let session_manager =
145 SessionManager::new(user_id.clone(), storage.clone(), provider.clone());
146 let group_manager = GroupManager::new(storage.clone(), provider.clone());
147
148 let manager = Self {
149 user_id,
150 storage,
151 provider,
152 credential: RwLock::new(None),
153 cached_signer: RwLock::new(None),
154 session_manager,
155 group_manager,
156 };
157
158 manager.ensure_identity()?;
159
160 Ok(manager)
161 }
162
163 pub fn user_id(&self) -> &str {
165 &self.user_id
166 }
167
168 pub fn load_or_create_identity(
184 storage: &Arc<dyn MlsStorage>,
185 ) -> Result<(SignatureKeyPair, Address)> {
186 let key_type = StorageKeyType::Identity.as_str();
187
188 let signature_keys = match storage.load(key_type, "key_pair")? {
189 Some(json) => serde_json::from_slice(&json).map_err(|e| {
190 MlsError::Deserialization(format!("Failed to deserialize signature keys: {}", e))
191 })?,
192 None => {
193 let keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
194 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
195 let keys_json = serde_json::to_vec(&keys)
196 .map_err(|e| MlsError::Serialization(e.to_string()))?;
197 storage.store(key_type, "key_pair", &keys_json)?;
198 info!("Minted a new identity key");
199 keys
200 }
201 };
202
203 let address = Self::derive_address(signature_keys.public())?;
204 Ok((signature_keys, address))
205 }
206
207 fn ensure_identity(&self) -> Result<()> {
209 if self.load_identity()? {
210 return Ok(());
211 }
212 self.create_identity()?;
213 info!(user_id = %self.user_id, "Created new MLS identity");
214 Ok(())
215 }
216
217 fn verify_identity_binding(&self, public_key: &[u8]) -> Result<()> {
224 let Ok(expected) = self.user_id.parse::<Address>() else {
225 return Ok(());
226 };
227
228 let derived = Self::derive_address(public_key)?;
229 if derived != expected {
230 return Err(MlsError::IdentityAddressMismatch {
231 expected: expected.to_string(),
232 derived: derived.to_string(),
233 });
234 }
235 Ok(())
236 }
237
238 fn load_identity(&self) -> Result<bool> {
240 let key_type = StorageKeyType::Identity.as_str();
241 let keys_data = self.storage.load(key_type, "key_pair")?;
242
243 match keys_data {
244 Some(json) => {
245 let signature_keys: SignatureKeyPair =
246 serde_json::from_slice(&json).map_err(|e| {
247 MlsError::Deserialization(format!(
248 "Failed to deserialize signature keys: {}",
249 e
250 ))
251 })?;
252
253 let public_key = signature_keys.public();
254
255 self.verify_identity_binding(public_key)?;
260
261 let credential =
262 Credential::new(CredentialType::Basic, self.user_id.as_bytes().to_vec());
263
264 let credential_with_key = CredentialWithKey {
265 credential,
266 signature_key: public_key.into(),
267 };
268
269 {
270 let mut guard = self
271 .credential
272 .write()
273 .map_err(|_| MlsError::NotInitialized)?;
274 *guard = Some(credential_with_key);
275 }
276 {
277 let mut guard = self
278 .cached_signer
279 .write()
280 .map_err(|_| MlsError::NotInitialized)?;
281 *guard = Some(signature_keys);
282 }
283
284 debug!(user_id = %self.user_id, "Loaded existing MLS identity");
285 Ok(true)
286 }
287 None => Ok(false),
288 }
289 }
290
291 fn create_identity(&self) -> Result<()> {
293 let signature_keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
294 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
295
296 self.verify_identity_binding(signature_keys.public())?;
304
305 let keys_json = serde_json::to_vec(&signature_keys)
306 .map_err(|e| MlsError::Serialization(e.to_string()))?;
307
308 let key_type = StorageKeyType::Identity.as_str();
309 self.storage.store(key_type, "key_pair", &keys_json)?;
310
311 let public_key = signature_keys.public();
312
313 let credential = Credential::new(CredentialType::Basic, self.user_id.as_bytes().to_vec());
314
315 let credential_with_key = CredentialWithKey {
316 credential,
317 signature_key: public_key.into(),
318 };
319
320 {
321 let mut guard = self
322 .credential
323 .write()
324 .map_err(|_| MlsError::NotInitialized)?;
325 *guard = Some(credential_with_key);
326 }
327 {
328 let mut guard = self
329 .cached_signer
330 .write()
331 .map_err(|_| MlsError::NotInitialized)?;
332 *guard = Some(signature_keys);
333 }
334
335 Ok(())
336 }
337
338 fn get_credential(&self) -> Result<CredentialWithKey> {
340 let guard = self
341 .credential
342 .read()
343 .map_err(|_| MlsError::NotInitialized)?;
344 guard.clone().ok_or(MlsError::NotInitialized)
345 }
346
347 fn get_signer(&self) -> Result<SignatureKeyPair> {
352 let guard = self
353 .cached_signer
354 .read()
355 .map_err(|_| MlsError::NotInitialized)?;
356
357 let cached = guard.as_ref().ok_or(MlsError::NotInitialized)?;
358 let bytes =
359 serde_json::to_vec(cached).map_err(|e| MlsError::Serialization(e.to_string()))?;
360 serde_json::from_slice(&bytes).map_err(|e| MlsError::Deserialization(e.to_string()))
361 }
362
363 pub fn generate_key_package(&self) -> Result<KeyPackageBundle> {
369 self.generate_key_package_inner(false)
370 }
371
372 pub fn generate_publication_key_package(&self) -> Result<KeyPackageBundle> {
379 self.generate_key_package_inner(true)
380 }
381
382 fn generate_key_package_inner(&self, reserved: bool) -> Result<KeyPackageBundle> {
383 let credential = self.get_credential()?;
384 let signature_keys = self.get_signer()?;
385
386 let key_package_bundle = KeyPackage::builder()
391 .key_package_lifetime(Lifetime::new(DEFAULT_KEY_PACKAGE_LIFETIME_SECS))
392 .build(
393 DEFAULT_CIPHERSUITE,
394 &self.provider,
395 &signature_keys,
396 credential,
397 )
398 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?;
399
400 let key_package_data = key_package_bundle
401 .key_package()
402 .tls_serialize_detached()
403 .map_err(|e| MlsError::Serialization(e.to_string()))?;
404
405 let provider_hash_ref = key_package_bundle
410 .key_package()
411 .hash_ref(self.provider.crypto())
412 .ok()
413 .and_then(|hash_ref| hash_ref.tls_serialize_detached().ok());
414
415 let package_id = Uuid::new_v4().to_string();
416
417 let key_type = StorageKeyType::KeyPackage.as_str();
418 let mut bundle = KeyPackageBundle::new(
419 package_id,
420 self.user_id.clone(),
421 key_package_data,
422 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
423 );
424 bundle.reserved_for_publication = reserved;
425 bundle.provider_hash_ref = provider_hash_ref;
426 let serialized =
427 serde_json::to_vec(&bundle).map_err(|e| MlsError::Serialization(e.to_string()))?;
428 self.storage
429 .store(key_type, &bundle.package_id, &serialized)?;
430
431 debug!(package_id = %bundle.package_id, "Generated new key package");
432 Ok(bundle)
433 }
434
435 pub fn get_or_create_key_package(&self) -> Result<KeyPackageBundle> {
454 let key_type = StorageKeyType::KeyPackage.as_str();
455 let packages = self.storage.list_keys(key_type)?;
456
457 for package_id in packages {
458 if let Some(bundle) = self.load_stored_key_package(&package_id)? {
459 if bundle.withheld_from_hand_out() || bundle.assigned_peer.is_some() {
460 continue;
461 }
462 return Ok(bundle);
463 }
464 }
465
466 self.generate_key_package()
467 }
468
469 pub fn take_push_key_package(&self, peer_id: &str) -> Result<PushKeyPackage> {
517 let key_type = StorageKeyType::KeyPackage.as_str();
518 let package_ids = self.storage.list_keys(key_type)?;
519
520 let mut live = 0usize;
521 let mut assigned_here: Option<KeyPackageBundle> = None;
522 let mut unclaimed: Option<KeyPackageBundle> = None;
523 let mut newest: Option<KeyPackageBundle> = None;
524
525 for package_id in package_ids {
529 let Some(bundle) = self.load_stored_key_package(&package_id)? else {
530 continue;
531 };
532 if bundle.withheld_from_hand_out() {
535 continue;
536 }
537 live += 1;
538
539 if bundle.assigned_peer.as_deref() == Some(peer_id) {
540 if newer_than(&bundle, assigned_here.as_ref()) {
542 assigned_here = Some(bundle.clone());
543 }
544 } else if bundle.assigned_peer.is_none() && newer_than(&bundle, unclaimed.as_ref()) {
545 unclaimed = Some(bundle.clone());
546 }
547
548 if newer_than(&bundle, newest.as_ref()) {
549 newest = Some(bundle);
550 }
551 }
552
553 if let Some(bundle) = assigned_here {
554 return Ok(PushKeyPackage {
555 bundle,
556 pool_exhausted: false,
557 });
558 }
559
560 if let Some(bundle) = unclaimed {
563 return Ok(PushKeyPackage {
564 bundle: self.claim_key_package(bundle, peer_id)?,
565 pool_exhausted: false,
566 });
567 }
568
569 if live >= MAX_PUSH_KEY_PACKAGES {
572 if let Some(bundle) = newest {
575 debug!(
576 peer_id = %peer_id,
577 live = live,
578 "Push key-package pool at capacity; reusing an existing package"
579 );
580 return Ok(PushKeyPackage {
581 bundle,
582 pool_exhausted: true,
583 });
584 }
585 }
586
587 let bundle = self.claim_key_package(self.generate_key_package()?, peer_id)?;
588
589 Ok(PushKeyPackage {
590 bundle,
591 pool_exhausted: false,
592 })
593 }
594
595 fn claim_key_package(
597 &self,
598 mut bundle: KeyPackageBundle,
599 peer_id: &str,
600 ) -> Result<KeyPackageBundle> {
601 bundle.assigned_peer = Some(peer_id.to_string());
602 let serialized =
603 serde_json::to_vec(&bundle).map_err(|e| MlsError::Serialization(e.to_string()))?;
604 self.storage.store(
605 StorageKeyType::KeyPackage.as_str(),
606 &bundle.package_id,
607 &serialized,
608 )?;
609 debug!(
610 package_id = %bundle.package_id,
611 peer_id = %peer_id,
612 "Assigned key package to peer"
613 );
614 Ok(bundle)
615 }
616
617 pub fn key_package_by_id(&self, package_id: &str) -> Result<Option<KeyPackageBundle>> {
625 self.load_stored_key_package(package_id)
626 }
627
628 pub fn delete_key_package(&self, package_id: &str) -> Result<()> {
641 let Some(data) = self
642 .storage
643 .load(StorageKeyType::KeyPackage.as_str(), package_id)?
644 else {
645 return Ok(());
646 };
647
648 match serde_json::from_slice::<KeyPackageBundle>(&data) {
649 Ok(bundle) => self.purge_key_package_material(package_id, &bundle),
650 Err(_) => {
658 let legacy = KeyPackageBundle::new(
659 package_id.to_string(),
660 self.user_id.clone(),
661 data,
662 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
663 );
664 self.purge_key_package_material(package_id, &legacy)
665 }
666 }
667 }
668
669 pub fn import_key_package(&self, user_id: &str, key_package_data: &[u8]) -> Result<()> {
684 offline_protocol_core::validate_id_chars(user_id, "User ID")
685 .map_err(|e| MlsError::InvalidUserId(e.to_string()))?;
686
687 let key_package = self.validate_peer_key_package(key_package_data)?;
688
689 Self::verify_credential_identity(&key_package, user_id)?;
690 Self::verify_address_binding(&key_package, user_id)?;
691 Self::verify_lifetime_bound(&key_package)?;
692
693 let key_type = StorageKeyType::ContactKeyPackage.as_str();
694 self.storage.store(key_type, user_id, key_package_data)?;
695
696 debug!(user_id = %user_id, "Imported key package for contact");
697 Ok(())
698 }
699
700 fn get_contact_key_package(&self, user_id: &str) -> Result<KeyPackage> {
702 let key_type = StorageKeyType::ContactKeyPackage.as_str();
703 let data = self
704 .storage
705 .load(key_type, user_id)?
706 .ok_or_else(|| MlsError::NoKeyPackage(user_id.to_string()))?;
707
708 let key_package = self.validate_peer_key_package(&data)?;
710
711 Self::verify_credential_identity(&key_package, user_id)?;
717 Self::verify_address_binding(&key_package, user_id)?;
718 Self::verify_lifetime_bound(&key_package)?;
719
720 Ok(key_package)
721 }
722
723 fn validate_peer_key_package(&self, key_package_data: &[u8]) -> Result<KeyPackage> {
737 KeyPackageIn::tls_deserialize_exact(key_package_data)
738 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?
739 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
740 .map_err(|e| match e {
741 KeyPackageVerifyError::InvalidLifetime => {
742 MlsError::KeyPackageOutsideValidityWindow {
743 expired: Self::window_has_closed(key_package_data),
744 }
745 }
746 other => MlsError::InvalidKeyPackage(other.to_string()),
747 })
748 }
749
750 fn window_has_closed(key_package_data: &[u8]) -> bool {
767 fn find_lifetime(value: &serde_json::Value) -> Option<u64> {
768 match value {
769 serde_json::Value::Object(map) => {
770 if map
771 .get("not_before")
772 .and_then(serde_json::Value::as_u64)
773 .is_some()
774 {
775 if let Some(not_after) =
776 map.get("not_after").and_then(serde_json::Value::as_u64)
777 {
778 return Some(not_after);
779 }
780 }
781 map.values().find_map(find_lifetime)
782 }
783 serde_json::Value::Array(items) => items.iter().find_map(find_lifetime),
784 _ => None,
785 }
786 }
787
788 let Some(not_after) = KeyPackageIn::tls_deserialize_exact(key_package_data)
789 .ok()
790 .and_then(|package| serde_json::to_value(&package).ok())
791 .and_then(|value| find_lifetime(&value))
792 else {
793 return false;
794 };
795 offline_protocol_core::time::SystemTime::now()
796 .duration_since(offline_protocol_core::time::UNIX_EPOCH)
797 .map(|now| now.as_secs() >= not_after)
798 .unwrap_or(false)
799 }
800
801 fn verify_lifetime_bound(key_package: &KeyPackage) -> Result<()> {
832 let lifetime = key_package.life_time();
833 let width = lifetime.not_after().saturating_sub(lifetime.not_before());
834 let cap = MAX_ACCEPTED_KEY_PACKAGE_LIFETIME.as_secs();
835
836 if width > cap {
837 return Err(MlsError::InvalidKeyPackage(format!(
838 "key package validity window is {} seconds, wider than the {} \
839 seconds this install admits",
840 width, cap
841 )));
842 }
843 Ok(())
844 }
845
846 fn verify_address_binding(key_package: &KeyPackage, user_id: &str) -> Result<()> {
875 let claimed = user_id.parse::<Address>().map_err(|e| {
876 MlsError::InvalidUserId(format!("'{}' is not an address: {}", user_id, e))
877 })?;
878
879 let derived = Self::derive_address(key_package.leaf_node().signature_key().as_slice())?;
880 if derived != claimed {
881 return Err(MlsError::KeyPackageAddressMismatch {
882 claimed: claimed.to_string(),
883 derived: derived.to_string(),
884 });
885 }
886 Ok(())
887 }
888
889 fn verify_credential_identity(key_package: &KeyPackage, user_id: &str) -> Result<()> {
893 let identity = key_package.leaf_node().credential().serialized_content();
894 if identity != user_id.as_bytes() {
895 return Err(MlsError::CredentialIdentityMismatch {
896 expected: user_id.to_string(),
897 found: String::from_utf8_lossy(identity).into_owned(),
898 });
899 }
900 Ok(())
901 }
902
903 pub fn get_pending_key_packages(&self) -> Result<Vec<KeyPackageBundle>> {
913 let key_type = StorageKeyType::KeyPackage.as_str();
914 let package_ids = self.storage.list_keys(key_type)?;
915
916 let mut bundles = Vec::new();
917 for package_id in package_ids {
918 if let Some(bundle) = self.load_stored_key_package(&package_id)? {
919 if bundle.withheld_from_hand_out() || bundle.assigned_peer.is_some() {
920 continue;
921 }
922 bundles.push(bundle);
923 }
924 }
925
926 Ok(bundles)
927 }
928
929 pub fn mark_key_package_synced(&mut self, package_id: &str) -> Result<()> {
956 let Some(mut bundle) = self.load_stored_key_package(package_id)? else {
957 debug!(
958 package_id = %package_id,
959 "No live key package to mark synced"
960 );
961 return Ok(());
962 };
963 if bundle.synced {
964 return Ok(());
965 }
966 if let Some(peer) = bundle.assigned_peer.as_deref() {
967 warn!(
968 package_id = %package_id,
969 peer_id = %peer,
970 "Marking a key package synced that the push path already handed to a peer"
971 );
972 }
973 bundle.synced = true;
974 let serialized =
975 serde_json::to_vec(&bundle).map_err(|e| MlsError::Serialization(e.to_string()))?;
976 self.storage
977 .store(StorageKeyType::KeyPackage.as_str(), package_id, &serialized)?;
978 debug!(package_id = %package_id, "Marked key package as synced");
979 Ok(())
980 }
981
982 pub fn has_session(&self, other_user_id: &str) -> Result<bool> {
988 self.session_manager.has_session(other_user_id)
989 }
990
991 pub fn session_corroboration(
994 &self,
995 other_user_id: &str,
996 nonce: &[u8],
997 ) -> Result<Option<(u64, Vec<u8>)>> {
998 self.session_manager.corroboration(other_user_id, nonce)
999 }
1000
1001 pub fn welcome_targets_key_package(
1010 &self,
1011 welcome: &WelcomeMessage,
1012 key_package_data: &[u8],
1013 ) -> Result<bool> {
1014 let reference = self
1015 .validate_peer_key_package(key_package_data)?
1016 .hash_ref(self.provider.crypto())
1017 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
1018 let message = MlsMessageIn::tls_deserialize_exact(&welcome.welcome_data)
1019 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1020 let MlsMessageBodyIn::Welcome(welcome) = message.extract() else {
1021 return Ok(false);
1022 };
1023 Ok(welcome
1024 .secrets()
1025 .iter()
1026 .any(|secrets| secrets.new_member() == reference))
1027 }
1028
1029 pub fn create_session(&self, other_user_id: &str) -> Result<WelcomeMessage> {
1031 let their_key_package = self.get_contact_key_package(other_user_id)?;
1032 let credential = self.get_credential()?;
1033 let signature_keys = self.get_signer()?;
1034
1035 let welcome = self.session_manager.create_session(
1036 other_user_id,
1037 their_key_package,
1038 &credential,
1039 &signature_keys,
1040 )?;
1041
1042 let key_type = StorageKeyType::ContactKeyPackage.as_str();
1043 self.storage.delete(key_type, other_user_id)?;
1044 self.stamp_session_established(&welcome.group_id);
1045
1046 Ok(welcome)
1047 }
1048
1049 pub fn join_session(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1051 let info = self.session_manager.join_session(welcome)?;
1052 self.stamp_session_established(&welcome.group_id);
1053 Ok(info)
1054 }
1055
1056 pub fn session_established_at_ms(&self, other_user_id: &str) -> Result<Option<u64>> {
1064 let session_id = self.session_manager.get_session_id(other_user_id)?;
1065 Ok(self
1066 .load_group_metadata(&session_id)?
1067 .map(|metadata| metadata.created_at_ms))
1068 }
1069
1070 fn stamp_session_established(&self, session_id: &GroupId) {
1075 if let Err(e) = self.save_group_metadata(session_id, &GroupMetadata::new(None)) {
1076 warn!(session_id = %session_id, error = %e, "Failed to stamp session establishment time");
1077 }
1078 }
1079
1080 pub fn replace_session_with_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1087 let other_user_id = &welcome.inviter_id;
1088
1089 offline_protocol_core::validate_id_chars(other_user_id, "User ID")
1093 .map_err(|e| MlsError::InvalidUserId(e.to_string()))?;
1094
1095 self.session_manager.verify_welcome_slot(welcome)?;
1100
1101 let _ = self.clear_pending_welcome(other_user_id);
1103
1104 let key_type = StorageKeyType::ContactKeyPackage.as_str();
1106 let _ = self.storage.delete(key_type, other_user_id);
1107
1108 self.join_session(welcome)
1112 }
1113
1114 pub fn encrypt_for_user(
1116 &self,
1117 other_user_id: &str,
1118 plaintext: &[u8],
1119 ) -> Result<EncryptedMessage> {
1120 if !self.has_session(other_user_id)? {
1121 let welcome = self.create_session(other_user_id)?;
1122 warn!(
1123 other_user_id = %other_user_id,
1124 "Created new session - Welcome message needs to be sent"
1125 );
1126 let key_type = StorageKeyType::PendingWelcome.as_str();
1127 let welcome_data =
1128 serde_json::to_vec(&welcome).map_err(|e| MlsError::Serialization(e.to_string()))?;
1129 self.storage.store(key_type, other_user_id, &welcome_data)?;
1130 }
1131
1132 let signature_keys = self.get_signer()?;
1133 self.session_manager
1134 .encrypt_message(other_user_id, plaintext, &signature_keys)
1135 }
1136
1137 pub fn last_seal_mark(&self, other_user_id: &str) -> Option<crate::session::SealMark> {
1140 self.session_manager.last_seal_mark(other_user_id)
1141 }
1142
1143 pub fn note_acknowledged_seal(&self, other_user_id: &str, mark: crate::session::SealMark) {
1146 self.session_manager
1147 .note_acknowledged_seal(other_user_id, mark);
1148 }
1149
1150 pub fn unacknowledged_seals(&self, other_user_id: &str) -> u64 {
1153 self.session_manager.unacknowledged_seals(other_user_id)
1154 }
1155
1156 pub fn encrypt_for_existing_session(
1163 &self,
1164 other_user_id: &str,
1165 plaintext: &[u8],
1166 ) -> Result<EncryptedMessage> {
1167 let signature_keys = self.get_signer()?;
1168 self.session_manager
1169 .encrypt_message(other_user_id, plaintext, &signature_keys)
1170 }
1171
1172 pub fn get_pending_welcome(&self, other_user_id: &str) -> Result<Option<WelcomeMessage>> {
1174 let key_type = StorageKeyType::PendingWelcome.as_str();
1175 match self.storage.load(key_type, other_user_id)? {
1176 Some(data) => {
1177 let welcome: WelcomeMessage = serde_json::from_slice(&data)
1178 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1179 Ok(Some(welcome))
1180 }
1181 None => Ok(None),
1182 }
1183 }
1184
1185 pub fn clear_pending_welcome(&self, other_user_id: &str) -> Result<()> {
1187 let key_type = StorageKeyType::PendingWelcome.as_str();
1188 self.storage.delete(key_type, other_user_id)?;
1189 Ok(())
1190 }
1191
1192 pub fn decrypt_from_user(
1199 &self,
1200 encrypted: &EncryptedMessage,
1201 claimed_sender: &str,
1202 ) -> Result<Option<Vec<u8>>> {
1203 self.session_manager
1204 .decrypt_message(encrypted, claimed_sender)
1205 }
1206
1207 pub fn list_sessions(&self) -> Result<Vec<String>> {
1209 self.session_manager.list_sessions()
1210 }
1211
1212 pub fn delete_session(&self, other_user_id: &str) -> Result<()> {
1219 let session_id = self.session_manager.get_session_id(other_user_id)?;
1220 self.storage
1221 .delete(StorageKeyType::GroupMetadata.as_str(), session_id.as_str())?;
1222 self.session_manager.delete_session(other_user_id)
1223 }
1224
1225 pub fn create_group(&self, group_name: &str) -> Result<GroupInfo> {
1231 let group_id = GroupId::new(format!("group:{}", Uuid::new_v4()))?;
1232 let credential = self.get_credential()?;
1233 let signature_keys = self.get_signer()?;
1234
1235 let group = self
1236 .group_manager
1237 .create_group(&group_id, &credential, &signature_keys)?;
1238
1239 let metadata = GroupMetadata::new_with_creator(Some(group_name.to_string()), &self.user_id);
1241 self.save_group_metadata(&group_id, &metadata)?;
1242
1243 let mut info = self.group_manager.get_group_info(&group, &group_id);
1244 info.name = metadata.name;
1245 info.created_at_ms = metadata.created_at_ms;
1246 info.last_activity_ms = metadata.last_activity_ms;
1247
1248 info!(group_id = %group_id, name = %group_name, "Created new group");
1249 Ok(info)
1250 }
1251
1252 pub fn add_group_member(
1274 &self,
1275 group_id: &GroupId,
1276 invitee_user_id: &str,
1277 member_key_package: &[u8],
1278 ) -> Result<(WelcomeMessage, EncryptedMessage)> {
1279 let key_package = self.validate_peer_key_package(member_key_package)?;
1280
1281 Self::verify_credential_identity(&key_package, invitee_user_id)?;
1282 Self::verify_address_binding(&key_package, invitee_user_id)?;
1283 Self::verify_lifetime_bound(&key_package)?;
1284
1285 let mut group = self
1286 .group_manager
1287 .load_group(group_id)?
1288 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1289
1290 let signature_keys = self.get_signer()?;
1291 let (commit, welcome) =
1292 self.group_manager
1293 .add_member(&mut group, key_package, &signature_keys)?;
1294
1295 self.group_manager.save_group(group_id, &group)?;
1296
1297 let welcome_bytes = welcome
1298 .tls_serialize_detached()
1299 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1300
1301 let commit_bytes = commit
1302 .tls_serialize_detached()
1303 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1304
1305 let group_name = self.load_group_metadata(group_id)?.and_then(|m| m.name);
1307
1308 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
1309
1310 let welcome_msg = WelcomeMessage {
1311 group_id: group_id.clone(),
1312 welcome_data: welcome_bytes,
1313 inviter_id: self.user_id.clone(),
1314 group_name,
1315 timestamp_ms: now_ms,
1316 };
1317
1318 let commit_msg = EncryptedMessage {
1319 group_id: group_id.clone(),
1320 message_type: MlsMessageType::Commit,
1321 epoch: group.epoch().as_u64(),
1322 ciphertext: commit_bytes,
1323 sender_id: self.user_id.clone(),
1324 timestamp_ms: now_ms,
1325 };
1326
1327 Ok((welcome_msg, commit_msg))
1328 }
1329
1330 pub fn remove_group_member(
1357 &self,
1358 group_id: &GroupId,
1359 member_id: &str,
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 member_indices: Vec<LeafNodeIndex> = group
1367 .members()
1368 .filter(|m| m.credential.serialized_content() == member_id.as_bytes())
1369 .map(|m| m.index)
1370 .collect();
1371
1372 if member_indices.is_empty() {
1373 return Err(MlsError::UserNotInGroup(member_id.to_string()));
1374 }
1375
1376 let signature_keys = self.get_signer()?;
1377 let commit =
1378 self.group_manager
1379 .remove_member(&mut group, &member_indices, &signature_keys)?;
1380
1381 self.group_manager.save_group(group_id, &group)?;
1382
1383 let ciphertext = commit
1384 .tls_serialize_detached()
1385 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1386
1387 Ok(EncryptedMessage {
1388 group_id: group_id.clone(),
1389 message_type: MlsMessageType::Commit,
1390 epoch: group.epoch().as_u64(),
1391 ciphertext,
1392 sender_id: self.user_id.clone(),
1393 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1394 })
1395 }
1396
1397 pub fn leave_group(&self, group_id: &GroupId) -> Result<()> {
1399 self.group_manager.delete_group(group_id)?;
1400 info!(group_id = %group_id, "Left group");
1401 Ok(())
1402 }
1403
1404 pub fn encrypt_for_group(
1406 &self,
1407 group_id: &GroupId,
1408 plaintext: &[u8],
1409 ) -> Result<EncryptedMessage> {
1410 let mut group = self
1411 .group_manager
1412 .load_group(group_id)?
1413 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1414
1415 let signature_keys = self.get_signer()?;
1416 let mls_message =
1417 self.group_manager
1418 .encrypt_message(&mut group, plaintext, &signature_keys)?;
1419
1420 self.group_manager.save_group(group_id, &group)?;
1421
1422 let ciphertext = mls_message
1423 .tls_serialize_detached()
1424 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1425
1426 Ok(EncryptedMessage {
1427 group_id: group_id.clone(),
1428 message_type: MlsMessageType::Application,
1429 epoch: group.epoch().as_u64(),
1430 ciphertext,
1431 sender_id: self.user_id.clone(),
1432 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1433 })
1434 }
1435
1436 pub fn decrypt_from_group(
1444 &self,
1445 encrypted: &EncryptedMessage,
1446 claimed_sender: &str,
1447 ) -> Result<Option<Vec<u8>>> {
1448 let mut group = self
1449 .group_manager
1450 .load_group(&encrypted.group_id)?
1451 .ok_or_else(|| MlsError::GroupNotFound(encrypted.group_id.to_string()))?;
1452
1453 let mls_message = MlsMessageIn::tls_deserialize_exact(&encrypted.ciphertext)
1454 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1455
1456 let result = self.group_manager.decrypt_message(
1457 &mut group,
1458 &encrypted.group_id,
1459 mls_message,
1460 claimed_sender,
1461 )?;
1462
1463 self.group_manager.save_group(&encrypted.group_id, &group)?;
1464
1465 Ok(result)
1466 }
1467
1468 pub fn join_group(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1470 if welcome.group_id.as_str().starts_with("session:") {
1482 return Err(MlsError::ReservedSessionNamespace {
1483 group_id: welcome.group_id.to_string(),
1484 });
1485 }
1486
1487 let mls_msg = MlsMessageIn::tls_deserialize_exact(&welcome.welcome_data)
1488 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1489
1490 let welcome_msg = match mls_msg.extract() {
1491 MlsMessageBodyIn::Welcome(w) => w,
1492 _ => {
1493 return Err(MlsError::WelcomeProcessing(
1494 "Not a Welcome message".to_string(),
1495 ))
1496 }
1497 };
1498
1499 let group = self
1500 .group_manager
1501 .join_group(welcome_msg, &welcome.group_id)?;
1502 let mut info = self.group_manager.get_group_info(&group, &welcome.group_id);
1503 info.name = welcome.group_name.clone();
1504
1505 info!(group_id = %welcome.group_id, "Joined group");
1506 Ok(info)
1507 }
1508
1509 pub fn list_groups(&self) -> Result<Vec<GroupId>> {
1511 let all_groups = self.group_manager.list_groups()?;
1512 Ok(all_groups
1513 .into_iter()
1514 .filter(|g| !g.as_str().starts_with("session:"))
1515 .collect())
1516 }
1517
1518 pub fn get_group_info(&self, group_id: &GroupId) -> Result<Option<GroupInfo>> {
1520 let group = match self.group_manager.load_group(group_id)? {
1521 Some(g) => g,
1522 None => return Ok(None),
1523 };
1524
1525 let mut info = self.group_manager.get_group_info(&group, group_id);
1526
1527 if let Some(metadata) = self.load_group_metadata(group_id)? {
1529 info.name = metadata.name;
1530 info.created_at_ms = metadata.created_at_ms;
1531 info.last_activity_ms = metadata.last_activity_ms;
1532 }
1533
1534 Ok(Some(info))
1535 }
1536
1537 pub fn has_group(&self, group_id: &GroupId) -> Result<bool> {
1546 Ok(self.group_manager.load_group(group_id)?.is_some())
1547 }
1548
1549 pub fn set_group_name(&self, group_id: &GroupId, name: &str) -> Result<()> {
1551 let mut metadata = self
1552 .load_group_metadata(group_id)?
1553 .unwrap_or_else(|| GroupMetadata::new(None));
1554 metadata.name = Some(name.to_string());
1555 metadata.touch();
1556 self.save_group_metadata(group_id, &metadata)
1557 }
1558
1559 pub fn get_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1561 self.load_group_metadata(group_id)
1562 }
1563
1564 pub fn set_group_custom_metadata(
1566 &self,
1567 group_id: &GroupId,
1568 key: &str,
1569 value: &str,
1570 ) -> Result<()> {
1571 let mut metadata = self
1572 .load_group_metadata(group_id)?
1573 .unwrap_or_else(|| GroupMetadata::new(None));
1574 metadata.custom.insert(key.to_string(), value.to_string());
1575 metadata.touch();
1576 self.save_group_metadata(group_id, &metadata)
1577 }
1578
1579 pub fn remove_group_custom_metadata(&self, group_id: &GroupId, key: &str) -> Result<()> {
1581 let mut metadata = self
1582 .load_group_metadata(group_id)?
1583 .unwrap_or_else(|| GroupMetadata::new(None));
1584 metadata.custom.remove(key);
1585 metadata.touch();
1586 self.save_group_metadata(group_id, &metadata)
1587 }
1588
1589 pub fn set_member_role(
1591 &self,
1592 group_id: &GroupId,
1593 user_id: &str,
1594 role: GroupRole,
1595 ) -> Result<()> {
1596 let mut metadata = self
1597 .load_group_metadata(group_id)?
1598 .unwrap_or_else(|| GroupMetadata::new(None));
1599 metadata.set_role(user_id, role);
1600 metadata.touch();
1601 self.save_group_metadata(group_id, &metadata)
1602 }
1603
1604 pub fn set_enforce_admin_commits(&mut self, enforce: bool) {
1613 self.group_manager.set_enforce_admin_commits(enforce);
1614 }
1615
1616 pub fn set_group_creator(&self, group_id: &GroupId, creator_id: &str) -> Result<()> {
1630 let mut metadata = self
1631 .load_group_metadata(group_id)?
1632 .unwrap_or_else(|| GroupMetadata::new(None));
1633 if metadata.created_by.is_some() {
1634 return Ok(());
1635 }
1636 metadata.created_by = Some(creator_id.to_string());
1637 metadata.touch();
1638 self.save_group_metadata(group_id, &metadata)
1639 }
1640
1641 pub fn remove_member_role(&self, group_id: &GroupId, user_id: &str) -> Result<()> {
1643 let mut metadata = self
1644 .load_group_metadata(group_id)?
1645 .unwrap_or_else(|| GroupMetadata::new(None));
1646 metadata.remove_role(user_id);
1647 metadata.touch();
1648 self.save_group_metadata(group_id, &metadata)
1649 }
1650
1651 pub fn decrypt(
1661 &self,
1662 encrypted: &EncryptedMessage,
1663 claimed_sender: &str,
1664 ) -> Result<Option<Vec<u8>>> {
1665 if encrypted.group_id.as_str().starts_with("session:") {
1666 self.decrypt_from_user(encrypted, claimed_sender)
1667 } else {
1668 self.decrypt_from_group(encrypted, claimed_sender)
1669 }
1670 }
1671
1672 pub fn process_welcome(&self, welcome: &WelcomeMessage) -> Result<GroupInfo> {
1674 if welcome.group_id.as_str().starts_with("session:") {
1675 self.join_session(welcome)
1676 } else {
1677 self.join_group(welcome)
1678 }
1679 }
1680}
1681
1682impl MlsManager {
1683 fn load_stored_key_package(&self, package_id: &str) -> Result<Option<KeyPackageBundle>> {
1689 let key_type = StorageKeyType::KeyPackage.as_str();
1690 let data = match self.storage.load(key_type, package_id)? {
1691 Some(data) => data,
1692 None => return Ok(None),
1693 };
1694
1695 let mut bundle = match serde_json::from_slice::<KeyPackageBundle>(&data) {
1696 Ok(bundle) => bundle,
1697 Err(_) => {
1698 let legacy_bundle = KeyPackageBundle::new(
1699 package_id.to_string(),
1700 self.user_id.clone(),
1701 data,
1702 DEFAULT_KEY_PACKAGE_LIFETIME_SECS,
1703 );
1704 let serialized = serde_json::to_vec(&legacy_bundle)
1705 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1706 self.storage.store(key_type, package_id, &serialized)?;
1707 legacy_bundle
1708 }
1709 };
1710
1711 if bundle.is_expired() {
1712 if bundle.expired_past_grace(KEY_PACKAGE_PURGE_GRACE_SECS) {
1719 self.purge_key_package_material(package_id, &bundle)?;
1720 }
1721 return Ok(None);
1722 }
1723
1724 let needs_backfill = bundle.provider_hash_ref.is_none();
1730 let Some(hash_ref) = self.bundle_hash_ref(&bundle) else {
1731 warn!(
1732 package_id = %package_id,
1733 "Key package bytes no longer parse as a key package, pruning stale entry"
1734 );
1735 self.storage.delete(key_type, package_id)?;
1736 return Ok(None);
1737 };
1738 if !self.provider_has_init_key(&hash_ref) {
1739 warn!(
1740 package_id = %package_id,
1741 "Key package private key no longer in provider storage, pruning stale entry"
1742 );
1743 self.storage.delete(key_type, package_id)?;
1744 return Ok(None);
1745 }
1746
1747 if needs_backfill {
1748 if let Ok(bytes) = hash_ref.tls_serialize_detached() {
1749 bundle.provider_hash_ref = Some(bytes);
1750 let serialized = serde_json::to_vec(&bundle)
1751 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1752 self.storage.store(key_type, package_id, &serialized)?;
1753 }
1754 }
1755
1756 Ok(Some(bundle))
1757 }
1758
1759 fn purge_key_package_material(
1774 &self,
1775 package_id: &str,
1776 bundle: &KeyPackageBundle,
1777 ) -> Result<()> {
1778 if let Some(hash_ref) = self.bundle_hash_ref(bundle) {
1779 use openmls_traits::storage::StorageProvider;
1780 if let Err(e) = self.provider.storage().delete_key_package(&hash_ref) {
1781 warn!(
1782 package_id = %package_id,
1783 error = %e,
1784 "Failed to delete a key package's private init key; keeping the record to retry"
1785 );
1786 return Ok(());
1787 }
1788 }
1789
1790 self.storage
1791 .delete(StorageKeyType::KeyPackage.as_str(), package_id)?;
1792 debug!(package_id = %package_id, "Purged key package and its init key");
1793 Ok(())
1794 }
1795
1796 fn key_package_hash_ref(&self, key_package_data: &[u8]) -> Option<KeyPackageRef> {
1803 let kp_in = KeyPackageIn::tls_deserialize_exact(key_package_data).ok()?;
1804 let kp: KeyPackage = kp_in
1805 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
1806 .ok()?;
1807 kp.hash_ref(self.provider.crypto()).ok()
1808 }
1809
1810 fn bundle_hash_ref(&self, bundle: &KeyPackageBundle) -> Option<KeyPackageRef> {
1814 bundle
1815 .provider_hash_ref
1816 .as_deref()
1817 .and_then(|bytes| KeyPackageRef::tls_deserialize_exact(bytes).ok())
1818 .or_else(|| self.key_package_hash_ref(&bundle.key_package_data))
1819 }
1820
1821 fn provider_has_init_key(&self, hash_ref: &KeyPackageRef) -> bool {
1825 use openmls_traits::storage::StorageProvider;
1826 let found: std::result::Result<Option<openmls::key_packages::KeyPackageBundle>, _> =
1827 self.provider.storage().key_package(hash_ref);
1828 matches!(found, Ok(Some(_)))
1829 }
1830
1831 #[cfg(test)]
1836 fn is_key_package_usable(&self, key_package_data: &[u8]) -> bool {
1837 self.key_package_hash_ref(key_package_data)
1838 .map(|hash_ref| self.provider_has_init_key(&hash_ref))
1839 .unwrap_or(false)
1840 }
1841
1842 fn load_group_metadata(&self, group_id: &GroupId) -> Result<Option<GroupMetadata>> {
1844 let key_type = StorageKeyType::GroupMetadata.as_str();
1845 match self.storage.load(key_type, group_id.as_str())? {
1846 Some(data) => {
1847 let mut metadata: GroupMetadata = serde_json::from_slice(&data)
1848 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
1849 if metadata.roles.is_empty()
1851 && metadata
1852 .custom
1853 .keys()
1854 .any(|k| k.starts_with(GroupMetadata::LEGACY_ROLE_KEY_PREFIX))
1855 {
1856 metadata.migrate_legacy_roles();
1857 if let Err(e) = self.save_group_metadata(group_id, &metadata) {
1859 warn!(group_id = %group_id.as_str(), error = %e, "Failed to persist legacy role migration");
1860 }
1861 }
1862 Ok(Some(metadata))
1863 }
1864 None => Ok(None),
1865 }
1866 }
1867
1868 fn save_group_metadata(&self, group_id: &GroupId, metadata: &GroupMetadata) -> Result<()> {
1870 let key_type = StorageKeyType::GroupMetadata.as_str();
1871 let data =
1872 serde_json::to_vec(metadata).map_err(|e| MlsError::Serialization(e.to_string()))?;
1873 self.storage.store(key_type, group_id.as_str(), &data)?;
1874 Ok(())
1875 }
1876}
1877
1878impl MlsManager {
1883 pub fn update_keys(&self, group_id: &GroupId) -> Result<EncryptedMessage> {
1892 use openmls::treesync::LeafNodeParameters;
1893
1894 let mut group = self
1895 .group_manager
1896 .load_group(group_id)?
1897 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
1898
1899 let signature_keys = self.get_signer()?;
1900
1901 let bundle = group
1902 .self_update(
1903 &self.provider,
1904 &signature_keys,
1905 LeafNodeParameters::default(),
1906 )
1907 .map_err(|e| MlsError::OpenMls(format!("Self-update failed: {}", e)))?;
1908
1909 let (commit, _welcome, _group_info) = bundle.into_contents();
1910
1911 group
1912 .merge_pending_commit(&self.provider)
1913 .map_err(|e| MlsError::OpenMls(format!("Failed to merge self-update commit: {}", e)))?;
1914
1915 self.group_manager.save_group(group_id, &group)?;
1916
1917 if let Some(mut metadata) = self.load_group_metadata(group_id)? {
1919 metadata.touch();
1920 self.save_group_metadata(group_id, &metadata)?;
1921 }
1922
1923 let ciphertext = commit
1924 .tls_serialize_detached()
1925 .map_err(|e| MlsError::Serialization(e.to_string()))?;
1926
1927 debug!(group_id = %group_id, epoch = %group.epoch().as_u64(), "Updated group keys");
1928
1929 Ok(EncryptedMessage {
1930 group_id: group_id.clone(),
1931 message_type: MlsMessageType::Commit,
1932 epoch: group.epoch().as_u64(),
1933 ciphertext,
1934 sender_id: self.user_id.clone(),
1935 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
1936 })
1937 }
1938
1939 pub fn ensure_min_key_packages(&self, min: usize) -> Result<usize> {
1964 let min = min.min(MAX_PUSH_KEY_PACKAGES);
1965 let key_type = StorageKeyType::KeyPackage.as_str();
1966 let package_ids = self.storage.list_keys(key_type)?;
1967
1968 let mut valid_count = 0;
1970 for package_id in &package_ids {
1971 if self
1972 .load_stored_key_package(package_id)?
1973 .is_some_and(|bundle| !bundle.withheld_from_hand_out())
1974 {
1975 valid_count += 1;
1976 }
1977 }
1978
1979 let to_generate = min.saturating_sub(valid_count);
1981 for _ in 0..to_generate {
1982 self.generate_key_package()?;
1983 valid_count += 1;
1984 }
1985
1986 debug!(
1987 valid_count = valid_count,
1988 generated = to_generate,
1989 "Ensured minimum key packages"
1990 );
1991
1992 Ok(valid_count)
1993 }
1994
1995 pub fn count_valid_key_packages(&self) -> Result<usize> {
2002 let key_type = StorageKeyType::KeyPackage.as_str();
2003 let package_ids = self.storage.list_keys(key_type)?;
2004
2005 let mut count = 0;
2006 for package_id in package_ids {
2007 if self.load_stored_key_package(&package_id)?.is_some() {
2008 count += 1;
2009 }
2010 }
2011
2012 Ok(count)
2013 }
2014
2015 pub fn get_identity_public_key(&self) -> Result<Vec<u8>> {
2024 let credential = self.get_credential()?;
2025 Ok(credential.signature_key.as_slice().to_vec())
2026 }
2027
2028 pub fn sign_data(&self, data: &[u8]) -> Result<Vec<u8>> {
2041 use openmls_traits::signatures::Signer;
2042
2043 let signer = self.get_signer()?;
2044 let signature = signer
2045 .sign(data)
2046 .map_err(|e| MlsError::Signing(format!("Failed to sign data: {:?}", e)))?;
2047 Ok(signature.as_slice().to_vec())
2048 }
2049
2050 pub fn verify_signature(public_key: &[u8], data: &[u8], signature: &[u8]) -> Result<bool> {
2062 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
2063
2064 let verifying_key = VerifyingKey::try_from(public_key).map_err(|e| {
2066 MlsError::InvalidPublicKey(format!("Invalid Ed25519 public key: {}", e))
2067 })?;
2068
2069 let sig = Signature::try_from(signature).map_err(|e| {
2071 MlsError::VerificationFailed(format!("Invalid signature format: {}", e))
2072 })?;
2073
2074 match verifying_key.verify(data, &sig) {
2076 Ok(()) => Ok(true),
2077 Err(_) => Ok(false),
2078 }
2079 }
2080
2081 pub fn derive_address(public_key: &[u8]) -> Result<Address> {
2107 offline_protocol_sealed::derive_address(public_key).map_err(MlsError::from)
2108 }
2109
2110 #[deprecated(
2130 note = "use derive_address; this returns the self-certifying off1… address, no longer base58"
2131 )]
2132 pub fn derive_user_id_from_public_key(public_key: &[u8]) -> String {
2133 use sha2::{Digest, Sha256};
2134
2135 let hash = Sha256::digest(public_key);
2136 let mut truncated = [0u8; Address::HASH_LEN];
2137 truncated.copy_from_slice(&hash[..Address::HASH_LEN]);
2138 Address::from_hash_bytes(truncated).to_string()
2139 }
2140}
2141
2142#[cfg(feature = "test-utils")]
2151impl MlsManager {
2152 pub fn seat_forged_leaf_for_testing(
2166 &self,
2167 group_id: &GroupId,
2168 impersonated: &str,
2169 ) -> Result<Vec<u8>> {
2170 let forged_storage: Arc<dyn MlsStorage> = Arc::new(crate::storage::InMemoryStorage::new());
2171 let forged_provider = MlsProvider::new(MlsStorageAdapter::new(forged_storage));
2172 let forged_keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
2173 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
2174 forged_keys
2175 .store(forged_provider.storage())
2176 .map_err(|e| MlsError::CryptoGeneration(format!("storing forged key: {:?}", e)))?;
2177
2178 let credential = CredentialWithKey {
2179 credential: Credential::new(CredentialType::Basic, impersonated.as_bytes().to_vec()),
2180 signature_key: forged_keys.public().into(),
2181 };
2182 let key_package = KeyPackage::builder()
2183 .build(
2184 DEFAULT_CIPHERSUITE,
2185 &forged_provider,
2186 &forged_keys,
2187 credential,
2188 )
2189 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?
2190 .key_package()
2191 .tls_serialize_detached()
2192 .map_err(|e| MlsError::Serialization(e.to_string()))?;
2193
2194 let validated = KeyPackageIn::tls_deserialize_exact(&key_package)
2195 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?
2196 .validate(self.provider.crypto(), ProtocolVersion::Mls10)
2197 .map_err(|e| MlsError::InvalidKeyPackage(e.to_string()))?;
2198
2199 let mut group = self
2200 .group_manager
2201 .load_group(group_id)?
2202 .ok_or_else(|| MlsError::GroupNotFound(group_id.to_string()))?;
2203 let signer = self.get_signer()?;
2204 let (commit, _welcome) = self
2205 .group_manager
2206 .add_member(&mut group, validated, &signer)?;
2207 self.group_manager.save_group(group_id, &group)?;
2208
2209 commit
2210 .tls_serialize_detached()
2211 .map_err(|e| MlsError::Serialization(e.to_string()))
2212 }
2213
2214 pub fn key_package_with_window_for_testing(
2222 claimed: &str,
2223 not_before: u64,
2224 not_after: u64,
2225 ) -> Result<Vec<u8>> {
2226 let storage: Arc<dyn MlsStorage> = Arc::new(crate::storage::InMemoryStorage::new());
2227 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
2228 let keys = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm())
2229 .map_err(|e| MlsError::CryptoGeneration(format!("{:?}", e)))?;
2230 keys.store(provider.storage())
2231 .map_err(|e| MlsError::CryptoGeneration(format!("storing key: {:?}", e)))?;
2232 let credential = CredentialWithKey {
2233 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
2234 signature_key: keys.public().into(),
2235 };
2236 KeyPackage::builder()
2237 .key_package_lifetime(Lifetime::init(not_before, not_after))
2238 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
2239 .map_err(|e| MlsError::KeyPackageCreation(e.to_string()))?
2240 .key_package()
2241 .tls_serialize_detached()
2242 .map_err(|e| MlsError::Serialization(e.to_string()))
2243 }
2244}
2245
2246pub fn is_mls_framed(bytes: &[u8]) -> bool {
2255 MlsMessageIn::tls_deserialize_exact(bytes).is_ok()
2256}
2257
2258#[cfg(test)]
2259mod tests {
2260 use super::*;
2261 use crate::storage::InMemoryStorage;
2262
2263 fn create_test_manager(label: &str) -> MlsManager {
2272 create_addressed_manager(label).0
2273 }
2274
2275 fn addr(label: &str) -> String {
2277 test_identity(label).1.to_string()
2278 }
2279
2280 fn slot(a: &str, b: &str) -> GroupId {
2286 GroupId::for_session(&addr(a), &addr(b)).unwrap()
2287 }
2288
2289 pub(crate) fn test_identity(label: &str) -> (SignatureKeyPair, Address) {
2297 use sha2::{Digest, Sha256};
2298
2299 let seed: [u8; 32] = Sha256::digest(label.as_bytes()).into();
2300 let signing = ed25519_dalek::SigningKey::from_bytes(&seed);
2301 let public = signing.verifying_key().to_bytes().to_vec();
2302 let address = MlsManager::derive_address(&public).unwrap();
2303 let keys = SignatureKeyPair::from_raw(
2304 DEFAULT_CIPHERSUITE.signature_algorithm(),
2305 signing.to_bytes().to_vec(),
2306 public,
2307 );
2308 (keys, address)
2309 }
2310
2311 pub(crate) fn seed_identity(storage: &Arc<dyn MlsStorage>, keys: &SignatureKeyPair) {
2314 storage
2315 .store(
2316 StorageKeyType::Identity.as_str(),
2317 "key_pair",
2318 &serde_json::to_vec(keys).unwrap(),
2319 )
2320 .unwrap();
2321 }
2322
2323 fn create_addressed_manager(label: &str) -> (MlsManager, Address) {
2326 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
2327 let (keys, address) = test_identity(label);
2328 seed_identity(&storage, &keys);
2329 let manager = MlsManager::new(address.to_string(), storage).unwrap();
2330 (manager, address)
2331 }
2332
2333 fn create_test_manager_with_storage(user_id: &str) -> (MlsManager, Arc<InMemoryStorage>) {
2337 let storage = Arc::new(InMemoryStorage::new());
2338 let manager = MlsManager::new(user_id, storage.clone()).unwrap();
2339 (manager, storage)
2340 }
2341
2342 fn expire_package(storage: &InMemoryStorage, package_id: &str, secs_ago: u64) {
2344 let key_type = StorageKeyType::KeyPackage.as_str();
2345 let data = storage
2346 .load(key_type, package_id)
2347 .unwrap()
2348 .expect("package to age is not in storage");
2349 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2350 let now_ms = chrono::Utc::now().timestamp_millis() as u64;
2351 bundle.expires_at_ms = now_ms - (secs_ago * 1000);
2352 storage
2353 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2354 .unwrap();
2355 }
2356
2357 fn init_key_present(manager: &MlsManager, bundle: &KeyPackageBundle) -> bool {
2359 manager.is_key_package_usable(&bundle.key_package_data)
2360 }
2361
2362 fn strip_cached_ref(storage: &InMemoryStorage, package_id: &str) {
2365 let key_type = StorageKeyType::KeyPackage.as_str();
2366 let data = storage
2367 .load(key_type, package_id)
2368 .unwrap()
2369 .expect("package to strip is not in storage");
2370 let mut bundle: KeyPackageBundle = serde_json::from_slice(&data).unwrap();
2371 bundle.provider_hash_ref = None;
2372 storage
2373 .store(key_type, package_id, &serde_json::to_vec(&bundle).unwrap())
2374 .unwrap();
2375 }
2376
2377 #[test]
2382 fn test_minted_package_carries_its_provider_hash_ref() {
2383 let manager = create_test_manager("alice");
2384 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2385
2386 let cached = bundle
2387 .provider_hash_ref
2388 .as_deref()
2389 .expect("mint must stamp the provider hash ref");
2390 let derived = manager
2391 .key_package_hash_ref(&bundle.key_package_data)
2392 .expect("freshly minted package bytes must parse")
2393 .tls_serialize_detached()
2394 .unwrap();
2395 assert_eq!(
2396 cached,
2397 &derived[..],
2398 "cached ref disagrees with the one derived from the package bytes"
2399 );
2400 }
2401
2402 #[test]
2405 fn test_record_without_cached_ref_is_backfilled_on_first_load() {
2406 let (manager, storage) = create_test_manager_with_storage("alice");
2407 let minted = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2408 strip_cached_ref(&storage, &minted.package_id);
2409
2410 let loaded = manager
2411 .key_package_by_id(&minted.package_id)
2412 .unwrap()
2413 .expect("a record without a cached ref must still load");
2414 assert_eq!(
2415 loaded.provider_hash_ref, minted.provider_hash_ref,
2416 "load must hand back a bundle stamped with the same ref the mint computed"
2417 );
2418
2419 let stored: KeyPackageBundle = serde_json::from_slice(
2420 &storage
2421 .load(StorageKeyType::KeyPackage.as_str(), &minted.package_id)
2422 .unwrap()
2423 .unwrap(),
2424 )
2425 .unwrap();
2426 assert_eq!(
2427 stored.provider_hash_ref, minted.provider_hash_ref,
2428 "the backfilled ref must be persisted, not only returned"
2429 );
2430 }
2431
2432 #[test]
2435 fn test_purge_destroys_init_key_for_a_record_without_cached_ref() {
2436 let (manager, storage) = create_test_manager_with_storage("alice");
2437 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2438 strip_cached_ref(&storage, &bundle.package_id);
2439 expire_package(
2440 &storage,
2441 &bundle.package_id,
2442 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2443 );
2444
2445 assert!(manager
2446 .key_package_by_id(&bundle.package_id)
2447 .unwrap()
2448 .is_none());
2449 assert!(
2450 !init_key_present(&manager, &bundle),
2451 "the private init key outlived a pre-cache package"
2452 );
2453 }
2454
2455 #[test]
2460 fn test_expired_package_is_withdrawn_but_its_init_key_survives_the_grace_window() {
2461 let (manager, storage) = create_test_manager_with_storage("alice");
2462 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2463
2464 expire_package(&storage, &bundle.package_id, 60);
2465
2466 assert!(
2467 manager
2468 .key_package_by_id(&bundle.package_id)
2469 .unwrap()
2470 .is_none(),
2471 "an expired package must not be handed out"
2472 );
2473 assert!(
2474 init_key_present(&manager, &bundle),
2475 "the init key was destroyed on the expiry edge, breaking a late Welcome"
2476 );
2477
2478 let replacement = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2480 assert_ne!(replacement.package_id, bundle.package_id);
2481 }
2482
2483 #[test]
2488 fn test_package_past_the_grace_window_has_its_init_key_destroyed() {
2489 let (manager, storage) = create_test_manager_with_storage("alice");
2490 let bundle = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2491 assert!(init_key_present(&manager, &bundle));
2492
2493 expire_package(
2494 &storage,
2495 &bundle.package_id,
2496 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2497 );
2498
2499 assert!(manager
2501 .key_package_by_id(&bundle.package_id)
2502 .unwrap()
2503 .is_none());
2504
2505 assert!(
2506 !init_key_present(&manager, &bundle),
2507 "the private init key outlived its package"
2508 );
2509 assert!(
2510 storage
2511 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2512 .unwrap()
2513 .is_none(),
2514 "the bundle record outlived its key material"
2515 );
2516 }
2517
2518 #[test]
2522 fn test_delete_key_package_destroys_the_init_key() {
2523 let manager = create_test_manager("alice");
2524 let bundle = manager.generate_publication_key_package().unwrap();
2525 assert!(init_key_present(&manager, &bundle));
2526
2527 manager.delete_key_package(&bundle.package_id).unwrap();
2528
2529 assert!(
2530 !init_key_present(&manager, &bundle),
2531 "delete_key_package left the private init key in provider storage"
2532 );
2533 }
2534
2535 #[test]
2540 fn test_delete_key_package_destroys_the_init_key_of_a_legacy_record() {
2541 let (manager, storage) = create_test_manager_with_storage("alice");
2542 let bundle = manager.generate_publication_key_package().unwrap();
2543 storage
2545 .store(
2546 StorageKeyType::KeyPackage.as_str(),
2547 &bundle.package_id,
2548 &bundle.key_package_data,
2549 )
2550 .unwrap();
2551
2552 manager.delete_key_package(&bundle.package_id).unwrap();
2553
2554 assert!(
2555 !init_key_present(&manager, &bundle),
2556 "a legacy record's private init key survived its deletion"
2557 );
2558 assert!(
2559 storage
2560 .load(StorageKeyType::KeyPackage.as_str(), &bundle.package_id)
2561 .unwrap()
2562 .is_none(),
2563 "the legacy record outlived its key material"
2564 );
2565 }
2566
2567 #[test]
2571 fn test_delete_key_package_removes_an_unreadable_record() {
2572 let (manager, storage) = create_test_manager_with_storage("alice");
2573 storage
2574 .store(
2575 StorageKeyType::KeyPackage.as_str(),
2576 "junk",
2577 b"not a package",
2578 )
2579 .unwrap();
2580
2581 manager.delete_key_package("junk").unwrap();
2582
2583 assert!(storage
2584 .load(StorageKeyType::KeyPackage.as_str(), "junk")
2585 .unwrap()
2586 .is_none());
2587 }
2588
2589 #[test]
2595 fn test_ensure_min_key_packages_is_capped_at_the_pool_ceiling() {
2596 let manager = create_test_manager("alice");
2597
2598 let count = manager
2599 .ensure_min_key_packages(MAX_PUSH_KEY_PACKAGES + 50)
2600 .unwrap();
2601
2602 assert_eq!(count, MAX_PUSH_KEY_PACKAGES);
2603 assert_eq!(
2604 manager.count_valid_key_packages().unwrap(),
2605 MAX_PUSH_KEY_PACKAGES,
2606 "pre-filling minted past the ceiling the push path enforces"
2607 );
2608 }
2609
2610 #[test]
2611 fn test_manager_creation() {
2612 let manager = create_test_manager("alice");
2613 assert_eq!(manager.user_id(), addr("alice"));
2614 }
2615
2616 #[test]
2617 fn test_key_package_generation() {
2618 let manager = create_test_manager("alice");
2619 let package = manager.generate_key_package().unwrap();
2620
2621 assert_eq!(package.user_id, addr("alice"));
2622 assert!(!package.key_package_data.is_empty());
2623 assert!(!package.is_expired());
2624 }
2625
2626 #[test]
2631 fn test_get_or_create_never_returns_a_publication_package() {
2632 let manager = create_test_manager("alice");
2633 let reserved = manager.generate_publication_key_package().unwrap();
2634 assert!(reserved.reserved_for_publication);
2635
2636 let pushed = manager.get_or_create_key_package().unwrap();
2637 assert_ne!(
2638 pushed.package_id, reserved.package_id,
2639 "the push path took the package a published record is standing on"
2640 );
2641 assert!(!pushed.reserved_for_publication);
2642
2643 let pushed_again = manager.get_or_create_key_package().unwrap();
2646 assert_eq!(pushed.package_id, pushed_again.package_id);
2647 }
2648
2649 #[test]
2653 fn test_key_package_by_id_reports_presence() {
2654 let manager = create_test_manager("alice");
2655 let bundle = manager.generate_publication_key_package().unwrap();
2656
2657 let found = manager.key_package_by_id(&bundle.package_id).unwrap();
2658 assert!(found.is_some());
2659 assert_eq!(found.unwrap().package_id, bundle.package_id);
2660
2661 assert!(manager
2662 .key_package_by_id("no-such-package")
2663 .unwrap()
2664 .is_none());
2665 }
2666
2667 #[test]
2670 fn test_publication_packages_are_distinct_per_slot() {
2671 let manager = create_test_manager("alice");
2672 let a = manager.generate_publication_key_package().unwrap();
2673 let b = manager.generate_publication_key_package().unwrap();
2674 assert_ne!(a.package_id, b.package_id);
2675 assert_ne!(a.key_package_data, b.key_package_data);
2676 }
2677
2678 #[test]
2679 fn test_get_or_create_key_package() {
2680 let manager = create_test_manager("alice");
2681 let pkg1 = manager.get_or_create_key_package().unwrap();
2682 let pkg2 = manager.get_or_create_key_package().unwrap();
2683 assert_eq!(pkg1.package_id, pkg2.package_id);
2687 }
2688
2689 #[test]
2699 fn test_push_path_gives_each_peer_its_own_key_package() {
2700 let manager = create_test_manager("alice");
2701
2702 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap();
2703 let for_carol = manager.take_push_key_package(&addr("carol")).unwrap();
2704
2705 assert_ne!(
2706 for_bob.bundle.package_id, for_carol.bundle.package_id,
2707 "two peers were advertised the same key package"
2708 );
2709 assert_ne!(
2710 for_bob.bundle.key_package_data, for_carol.bundle.key_package_data,
2711 "two peers were advertised the same init key"
2712 );
2713 assert!(!for_bob.pool_exhausted && !for_carol.pool_exhausted);
2714 assert_eq!(
2715 for_bob.bundle.assigned_peer.as_deref(),
2716 Some(addr("bob").as_str())
2717 );
2718 assert_eq!(
2719 for_carol.bundle.assigned_peer.as_deref(),
2720 Some(addr("carol").as_str())
2721 );
2722 }
2723
2724 #[test]
2729 fn test_push_path_reuses_one_package_for_repeat_pushes_to_a_peer() {
2730 let manager = create_test_manager("alice");
2731
2732 let first = manager.take_push_key_package(&addr("bob")).unwrap();
2733 let second = manager.take_push_key_package(&addr("bob")).unwrap();
2734 let third = manager.take_push_key_package(&addr("bob")).unwrap();
2735
2736 assert_eq!(first.bundle.package_id, second.bundle.package_id);
2737 assert_eq!(first.bundle.package_id, third.bundle.package_id);
2738 assert_eq!(
2739 manager.count_valid_key_packages().unwrap(),
2740 1,
2741 "repeat pushes to one peer minted extra key material"
2742 );
2743 }
2744
2745 #[test]
2750 fn test_push_path_mints_a_fresh_package_after_the_peer_consumed_one() {
2751 let alice = create_test_manager("alice");
2752 let bob = create_test_manager("bob");
2753
2754 let advertised = alice.take_push_key_package(&addr("bob")).unwrap().bundle;
2755 bob.import_key_package(&addr("alice"), &advertised.key_package_data)
2756 .unwrap();
2757 let welcome = bob.create_session(&addr("alice")).unwrap();
2758 alice.join_session(&welcome).unwrap();
2759
2760 let next = alice.take_push_key_package(&addr("bob")).unwrap();
2761 assert_ne!(
2762 next.bundle.package_id, advertised.package_id,
2763 "a consumed package was advertised again"
2764 );
2765 assert!(!next.pool_exhausted);
2766 }
2767
2768 #[test]
2773 fn test_two_peers_can_both_establish_sessions_after_being_pushed_to() {
2774 let alice = create_test_manager("alice");
2775 let bob = create_test_manager("bob");
2776 let carol = create_test_manager("carol");
2777
2778 for (peer, manager) in [(addr("bob"), &bob), (addr("carol"), &carol)] {
2779 let advertised = alice.take_push_key_package(&peer).unwrap().bundle;
2780 manager
2781 .import_key_package(&addr("alice"), &advertised.key_package_data)
2782 .unwrap();
2783 }
2784
2785 let from_bob = bob.create_session(&addr("alice")).unwrap();
2788 let from_carol = carol.create_session(&addr("alice")).unwrap();
2789
2790 alice.join_session(&from_bob).unwrap();
2791 alice
2792 .join_session(&from_carol)
2793 .expect("second peer's Welcome must still be processable");
2794
2795 assert!(alice.has_session(&addr("bob")).unwrap());
2796 assert!(alice.has_session(&addr("carol")).unwrap());
2797 }
2798
2799 #[test]
2802 fn test_push_path_never_takes_a_publication_package() {
2803 let manager = create_test_manager("alice");
2804 let reserved = manager.generate_publication_key_package().unwrap();
2805
2806 for peer in [addr("bob"), addr("carol"), addr("dave")] {
2807 let taken = manager.take_push_key_package(&peer).unwrap();
2808 assert_ne!(
2809 taken.bundle.package_id, reserved.package_id,
2810 "the push path took the package a published record stands on"
2811 );
2812 }
2813 }
2814
2815 #[test]
2819 fn test_get_or_create_never_returns_a_peer_assigned_package() {
2820 let manager = create_test_manager("alice");
2821 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2822
2823 let unclaimed = manager.get_or_create_key_package().unwrap();
2824 assert_ne!(unclaimed.package_id, for_bob.package_id);
2825 assert!(unclaimed.assigned_peer.is_none());
2826 }
2827
2828 fn record_present(storage: &InMemoryStorage, package_id: &str) -> bool {
2830 storage
2831 .load(StorageKeyType::KeyPackage.as_str(), package_id)
2832 .unwrap()
2833 .is_some()
2834 }
2835
2836 #[test]
2840 fn test_marking_synced_keeps_the_record_and_the_init_key() {
2841 let (mut manager, storage) = create_test_manager_with_storage("alice");
2842 let bundle = manager.generate_key_package().unwrap();
2843
2844 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2845
2846 assert!(
2847 record_present(&storage, &bundle.package_id),
2848 "marking synced deleted the record, so nothing can ever expire the key"
2849 );
2850 assert!(
2851 init_key_present(&manager, &bundle),
2852 "marking synced destroyed the init key a Welcome from the upload needs"
2853 );
2854 let reread = manager
2855 .key_package_by_id(&bundle.package_id)
2856 .unwrap()
2857 .expect("a synced package is still live");
2858 assert!(reread.synced);
2859 }
2860
2861 #[test]
2865 fn test_unclaimed_synced_package_has_its_init_key_destroyed_past_the_grace_window() {
2866 let (mut manager, storage) = create_test_manager_with_storage("alice");
2867 let bundle = manager.generate_key_package().unwrap();
2868 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2869
2870 expire_package(
2871 &storage,
2872 &bundle.package_id,
2873 KEY_PACKAGE_PURGE_GRACE_SECS + 60,
2874 );
2875 assert!(manager
2876 .key_package_by_id(&bundle.package_id)
2877 .unwrap()
2878 .is_none());
2879
2880 assert!(
2881 !init_key_present(&manager, &bundle),
2882 "a synced package's init key outlived its grace window"
2883 );
2884 assert!(!record_present(&storage, &bundle.package_id));
2885 }
2886
2887 #[test]
2890 fn test_synced_package_is_withheld_from_both_hand_out_paths() {
2891 let mut manager = create_test_manager("alice");
2892 let synced = manager.generate_key_package().unwrap();
2893 manager.mark_key_package_synced(&synced.package_id).unwrap();
2894
2895 let pushed = manager.take_push_key_package(&addr("bob")).unwrap();
2896 assert_ne!(pushed.bundle.package_id, synced.package_id);
2897 assert!(!pushed.pool_exhausted);
2898
2899 let peerless = manager.get_or_create_key_package().unwrap();
2900 assert_ne!(peerless.package_id, synced.package_id);
2901 }
2902
2903 #[test]
2907 fn test_pending_list_holds_only_packages_the_application_may_publish() {
2908 let mut manager = create_test_manager("alice");
2909 let for_bob = manager.take_push_key_package(&addr("bob")).unwrap().bundle;
2912 let plain = manager.generate_key_package().unwrap();
2913 let reserved = manager.generate_publication_key_package().unwrap();
2914 let synced = manager.generate_key_package().unwrap();
2915 manager.mark_key_package_synced(&synced.package_id).unwrap();
2916
2917 let pending: Vec<String> = manager
2918 .get_pending_key_packages()
2919 .unwrap()
2920 .into_iter()
2921 .map(|b| b.package_id)
2922 .collect();
2923
2924 assert!(pending.contains(&plain.package_id));
2925 assert!(
2926 !pending.contains(&reserved.package_id),
2927 "lists a slot package"
2928 );
2929 assert!(
2930 !pending.contains(&for_bob.package_id),
2931 "lists a peer's package"
2932 );
2933 assert!(
2934 !pending.contains(&synced.package_id),
2935 "lists a synced package"
2936 );
2937 }
2938
2939 #[test]
2943 fn test_ensure_min_does_not_count_a_synced_package_toward_the_pool() {
2944 let mut manager = create_test_manager("alice");
2945 let synced = manager.generate_key_package().unwrap();
2946 manager.mark_key_package_synced(&synced.package_id).unwrap();
2947
2948 assert_eq!(manager.ensure_min_key_packages(1).unwrap(), 1);
2949 assert_eq!(
2950 manager.count_valid_key_packages().unwrap(),
2951 2,
2952 "a replacement was minted beside the synced package"
2953 );
2954 }
2955
2956 #[test]
2959 fn test_welcome_built_against_a_synced_package_still_opens() {
2960 let mut alice = create_test_manager("alice");
2961 let bob = create_test_manager("bob");
2962 let uploaded = alice.generate_key_package().unwrap();
2963 alice.mark_key_package_synced(&uploaded.package_id).unwrap();
2964
2965 bob.import_key_package(&addr("alice"), &uploaded.key_package_data)
2966 .unwrap();
2967 let welcome = bob.create_session(&addr("alice")).unwrap();
2968
2969 alice
2970 .join_session(&welcome)
2971 .expect("a Welcome against a synced package must open");
2972 assert!(alice.has_session(&addr("bob")).unwrap());
2973 }
2974
2975 #[test]
2978 fn test_marking_a_legacy_record_upgrades_and_marks_it() {
2979 let (mut manager, storage) = create_test_manager_with_storage("alice");
2980 let bundle = manager.generate_key_package().unwrap();
2981 storage
2982 .store(
2983 StorageKeyType::KeyPackage.as_str(),
2984 &bundle.package_id,
2985 &bundle.key_package_data,
2986 )
2987 .unwrap();
2988
2989 manager.mark_key_package_synced(&bundle.package_id).unwrap();
2990
2991 let reread = manager
2992 .key_package_by_id(&bundle.package_id)
2993 .unwrap()
2994 .expect("the legacy package is still live");
2995 assert!(reread.synced);
2996 assert!(init_key_present(&manager, &bundle));
2997 }
2998
2999 #[test]
3002 fn test_marking_an_unknown_or_consumed_package_is_a_no_op() {
3003 let mut alice = create_test_manager("alice");
3004 alice.mark_key_package_synced("no-such-package").unwrap();
3005
3006 let bob = create_test_manager("bob");
3007 let advertised = alice.take_push_key_package(&addr("bob")).unwrap().bundle;
3008 bob.import_key_package(&addr("alice"), &advertised.key_package_data)
3009 .unwrap();
3010 let welcome = bob.create_session(&addr("alice")).unwrap();
3011 alice.join_session(&welcome).unwrap();
3012
3013 alice
3014 .mark_key_package_synced(&advertised.package_id)
3015 .unwrap();
3016 assert!(alice
3017 .key_package_by_id(&advertised.package_id)
3018 .unwrap()
3019 .is_none());
3020 }
3021
3022 #[test]
3037 fn only_the_known_sites_delete_a_key_package_record_alone() {
3038 let allowed = ["load_stored_key_package", "purge_key_package_material"];
3039 let (checked, offenders) = record_only_key_package_deletes(include_str!("manager.rs"));
3040
3041 assert!(
3042 checked >= 8,
3043 "found only {checked} key package functions; the scan is broken, not the code"
3044 );
3045 let offenders: Vec<_> = offenders
3046 .into_iter()
3047 .filter(|name| !allowed.contains(&name.as_str()))
3048 .collect();
3049 assert!(
3050 offenders.is_empty(),
3051 "record-only delete of a key package in {offenders:?}. Destroy the init \
3052 key too (purge_key_package_material), or keep the record"
3053 );
3054 }
3055
3056 #[test]
3061 fn the_record_delete_scanner_sees_every_function_header_and_crlf() {
3062 let source = [
3063 "impl MlsManager {",
3064 " fn purge_key_package_material(&self) {",
3065 " let t = StorageKeyType::KeyPackage.as_str();",
3066 " self.storage.delete(t, id)?;",
3067 " }",
3068 " pub(crate) fn hidden_crate(&self) {",
3069 " self.storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3070 " }",
3071 " fn load_stored_key_package(&self) {}",
3072 " pub async fn hidden_async(&self) {",
3073 " self.storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3074 " }",
3075 "}",
3076 "fn hidden_free() {",
3077 " storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3078 "}",
3079 "#[cfg(test)]",
3080 "mod tests {",
3081 " fn in_tests() {",
3082 " storage.delete(StorageKeyType::KeyPackage.as_str(), id)?;",
3083 " }",
3084 "}",
3085 ]
3086 .join("\r\n");
3087
3088 let (checked, offenders) = record_only_key_package_deletes(&source);
3089
3090 assert_eq!(checked, 4, "the test module is out of scope");
3091 assert_eq!(
3092 offenders,
3093 [
3094 "purge_key_package_material",
3095 "hidden_crate",
3096 "hidden_async",
3097 "hidden_free"
3098 ]
3099 );
3100 }
3101
3102 fn record_only_key_package_deletes(source: &str) -> (usize, Vec<String>) {
3107 let lines: Vec<&str> = source.lines().collect();
3108 let test_module = lines
3109 .windows(2)
3110 .position(|pair| pair[0].trim() == "#[cfg(test)]" && pair[1].starts_with("mod tests"))
3111 .expect("the source has a test module");
3112
3113 let mut functions: Vec<(String, String)> = Vec::new();
3114 for line in &lines[..test_module] {
3115 if let Some(name) = function_header_name(line) {
3116 functions.push((name.to_string(), String::new()));
3117 } else if let Some((_, body)) = functions.last_mut() {
3118 body.push_str(line);
3119 body.push('\n');
3120 }
3121 }
3122
3123 let mut checked = 0;
3124 let mut offenders = Vec::new();
3125 for (name, body) in functions {
3126 if !body.contains("StorageKeyType::KeyPackage.as_str()") {
3127 continue;
3128 }
3129 checked += 1;
3130 if body.contains(".delete(") {
3131 offenders.push(name);
3132 }
3133 }
3134 (checked, offenders)
3135 }
3136
3137 fn function_header_name(line: &str) -> Option<&str> {
3140 let trimmed = line.trim_start();
3141 if !matches!(line.len() - trimmed.len(), 0 | 4) {
3142 return None;
3143 }
3144 let mut rest = trimmed;
3145 loop {
3146 if let Some(after) = rest.strip_prefix("pub(") {
3147 rest = after.split_once(')')?.1.trim_start();
3148 } else if let Some(after) = ["pub ", "async ", "const ", "unsafe "]
3149 .iter()
3150 .find_map(|qualifier| rest.strip_prefix(qualifier))
3151 {
3152 rest = after.trim_start();
3153 } else {
3154 break;
3155 }
3156 }
3157 rest.strip_prefix("fn ")?.split(['(', '<']).next()
3158 }
3159
3160 #[test]
3164 fn test_push_path_claims_an_unclaimed_package_instead_of_stranding_it() {
3165 let manager = create_test_manager("alice");
3166 let legacy = manager.get_or_create_key_package().unwrap();
3167 assert!(legacy.assigned_peer.is_none());
3168
3169 let taken = manager.take_push_key_package(&addr("bob")).unwrap();
3170 assert_eq!(
3171 taken.bundle.package_id, legacy.package_id,
3172 "the pre-existing package was stranded instead of claimed"
3173 );
3174 assert_eq!(
3175 taken.bundle.assigned_peer.as_deref(),
3176 Some(addr("bob").as_str())
3177 );
3178 assert_eq!(manager.count_valid_key_packages().unwrap(), 1);
3179 }
3180
3181 #[test]
3186 fn test_push_pool_ceiling_shares_a_package_and_reports_it() {
3187 let manager = create_test_manager("alice");
3188
3189 for i in 0..MAX_PUSH_KEY_PACKAGES {
3190 let taken = manager.take_push_key_package(&format!("peer{i}")).unwrap();
3191 assert!(
3192 !taken.pool_exhausted,
3193 "pool reported exhausted at {i}, below the ceiling"
3194 );
3195 }
3196 assert_eq!(
3197 manager.count_valid_key_packages().unwrap(),
3198 MAX_PUSH_KEY_PACKAGES
3199 );
3200
3201 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
3202 assert!(over.pool_exhausted, "ceiling breach was not reported");
3203 assert_eq!(
3204 manager.count_valid_key_packages().unwrap(),
3205 MAX_PUSH_KEY_PACKAGES,
3206 "the pool grew past its ceiling"
3207 );
3208
3209 let established = manager.take_push_key_package("peer0").unwrap();
3212 assert!(!established.pool_exhausted);
3213 assert_eq!(established.bundle.assigned_peer.as_deref(), Some("peer0"));
3214 }
3215
3216 #[test]
3222 fn test_ceiling_claims_an_unclaimed_package_rather_than_sharing() {
3223 let manager = create_test_manager("alice");
3224
3225 for i in 0..(MAX_PUSH_KEY_PACKAGES - 1) {
3226 manager.take_push_key_package(&format!("peer{i}")).unwrap();
3227 }
3228 let unclaimed = manager.get_or_create_key_package().unwrap();
3231 assert_eq!(
3232 manager.count_valid_key_packages().unwrap(),
3233 MAX_PUSH_KEY_PACKAGES,
3234 "test premise: the pool is at the ceiling"
3235 );
3236
3237 let taken = manager.take_push_key_package("late-arrival").unwrap();
3238 assert!(
3239 !taken.pool_exhausted,
3240 "shared a package while a claimable one was in the pool"
3241 );
3242 assert_eq!(
3243 taken.bundle.package_id, unclaimed.package_id,
3244 "the claimable package was passed over"
3245 );
3246 assert_eq!(taken.bundle.assigned_peer.as_deref(), Some("late-arrival"));
3247 assert_eq!(
3248 manager.count_valid_key_packages().unwrap(),
3249 MAX_PUSH_KEY_PACKAGES,
3250 "claiming grew the pool"
3251 );
3252
3253 let over = manager.take_push_key_package("one-peer-too-many").unwrap();
3256 assert!(over.pool_exhausted);
3257 }
3258
3259 #[test]
3260 fn test_import_key_package_binds_credential_identity() {
3261 let alice = create_test_manager("alice");
3262 let mallory = create_test_manager("mallory");
3263 let mallory_kp = mallory.generate_key_package().unwrap();
3264
3265 let err = alice
3268 .import_key_package(&addr("bob"), &mallory_kp.key_package_data)
3269 .unwrap_err();
3270 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3271
3272 alice
3274 .import_key_package(&addr("mallory"), &mallory_kp.key_package_data)
3275 .unwrap();
3276 }
3277
3278 fn substituted_key_package(claimed: &str, keys: &SignatureKeyPair) -> Vec<u8> {
3288 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3289 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
3290 keys.store(provider.storage()).unwrap();
3291
3292 let credential = CredentialWithKey {
3293 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
3294 signature_key: keys.public().into(),
3295 };
3296
3297 KeyPackage::builder()
3298 .build(DEFAULT_CIPHERSUITE, &provider, keys, credential)
3299 .unwrap()
3300 .key_package()
3301 .tls_serialize_detached()
3302 .unwrap()
3303 }
3304
3305 #[test]
3306 fn test_credential_identity_check_alone_does_not_stop_a_substitution() {
3307 let (mallorys_keys, _) = test_identity("mallory");
3312 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3313
3314 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3315 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
3316 let key_package = KeyPackageIn::tls_deserialize_exact(&forged)
3317 .unwrap()
3318 .validate(provider.crypto(), ProtocolVersion::Mls10)
3319 .unwrap();
3320
3321 MlsManager::verify_credential_identity(&key_package, &addr("bob"))
3322 .expect("the identity check cannot tell the two bobs apart");
3323 }
3324
3325 #[test]
3326 fn test_substituted_key_package_is_refused_by_the_derivation() {
3327 let alice = create_test_manager("alice");
3332 let real_bob = create_test_manager("bob");
3333 let (mallorys_keys, _) = test_identity("mallory");
3334
3335 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3336 let err = alice.import_key_package(&addr("bob"), &forged).unwrap_err();
3337 assert!(
3338 matches!(
3339 err,
3340 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
3341 ),
3342 "expected an address mismatch, got {:?}",
3343 err
3344 );
3345
3346 let real_kp = real_bob.generate_key_package().unwrap();
3348 alice
3349 .import_key_package(&addr("bob"), &real_kp.key_package_data)
3350 .expect("the real peer's key package derives to its own address");
3351 }
3352
3353 #[test]
3354 fn test_a_nickname_peer_id_is_refused_rather_than_skipping_the_check() {
3355 let alice = create_test_manager("alice");
3359 let bob_kp = create_test_manager("bob").generate_key_package().unwrap();
3360
3361 let err = alice
3362 .import_key_package("bob", &bob_kp.key_package_data)
3363 .unwrap_err();
3364 assert!(
3365 matches!(
3366 err,
3367 MlsError::CredentialIdentityMismatch { .. } | MlsError::InvalidUserId(_)
3368 ),
3369 "a nickname must be refused, got {:?}",
3370 err
3371 );
3372 }
3373
3374 #[test]
3375 fn test_add_group_member_binds_identity_and_derivation() {
3376 let alice = create_test_manager("alice");
3380 let bob = create_test_manager("bob");
3381 let mallory = create_test_manager("mallory");
3382
3383 let group_id = alice.create_group("Test Group").unwrap().group_id;
3384
3385 let mallory_kp = mallory.generate_key_package().unwrap();
3387 let err = alice
3388 .add_group_member(&group_id, &addr("bob"), &mallory_kp.key_package_data)
3389 .unwrap_err();
3390 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3391
3392 let (mallorys_keys, _) = test_identity("mallory");
3394 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3395 let err = alice
3396 .add_group_member(&group_id, &addr("bob"), &forged)
3397 .unwrap_err();
3398 assert!(matches!(err, MlsError::KeyPackageAddressMismatch { .. }));
3399
3400 let bob_kp = bob.generate_key_package().unwrap();
3402 alice
3403 .add_group_member(&group_id, &addr("bob"), &bob_kp.key_package_data)
3404 .expect("the real invitee is admitted");
3405 }
3406
3407 #[test]
3408 fn test_import_key_package_rejects_hostile_user_id() {
3409 let alice = create_test_manager("alice");
3410 let bob = create_test_manager("bob");
3411 let bob_kp = bob.generate_key_package().unwrap();
3412
3413 for hostile in ["", "..", "bob/evil", "bob\\evil", "bob\0evil"] {
3414 let err = alice
3415 .import_key_package(hostile, &bob_kp.key_package_data)
3416 .unwrap_err();
3417 assert!(
3418 matches!(err, MlsError::InvalidUserId(_)),
3419 "expected InvalidUserId for {:?}, got {:?}",
3420 hostile,
3421 err
3422 );
3423 }
3424 }
3425
3426 #[test]
3427 fn test_get_contact_key_package_rejects_poisoned_store() {
3428 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3432 let (alice_keys, alice_address) = test_identity("alice");
3433 seed_identity(&storage, &alice_keys);
3434 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
3435 let mallory = create_test_manager("mallory");
3436 let mallory_kp = mallory.generate_key_package().unwrap();
3437
3438 storage
3439 .store(
3440 StorageKeyType::ContactKeyPackage.as_str(),
3441 &addr("bob"),
3442 &mallory_kp.key_package_data,
3443 )
3444 .unwrap();
3445
3446 let err = alice.create_session(&addr("bob")).unwrap_err();
3448 assert!(matches!(err, MlsError::CredentialIdentityMismatch { .. }));
3449 }
3450
3451 #[test]
3470 fn test_get_contact_key_package_rejects_a_substituted_package() {
3471 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
3472 let (alice_keys, alice_address) = test_identity("alice");
3473 seed_identity(&storage, &alice_keys);
3474 let alice = MlsManager::new(alice_address.to_string(), storage.clone()).unwrap();
3475
3476 let (mallorys_keys, _) = test_identity("mallory");
3478 let forged = substituted_key_package(&addr("bob"), &mallorys_keys);
3479
3480 storage
3481 .store(
3482 StorageKeyType::ContactKeyPackage.as_str(),
3483 &addr("bob"),
3484 &forged,
3485 )
3486 .unwrap();
3487
3488 let err = alice.create_session(&addr("bob")).unwrap_err();
3489 assert!(
3490 matches!(
3491 err,
3492 MlsError::KeyPackageAddressMismatch { ref claimed, .. } if *claimed == addr("bob")
3493 ),
3494 "a substituted cached package must be refused at use time, got {:?}",
3495 err
3496 );
3497
3498 assert!(!alice.has_session(&addr("bob")).unwrap());
3500 }
3501
3502 #[test]
3503 fn test_replace_session_with_welcome_rejects_hostile_inviter_id() {
3504 let alice = create_test_manager("alice");
3508 let welcome = WelcomeMessage {
3509 group_id: slot("alice", "bob"),
3510 welcome_data: vec![],
3511 inviter_id: "../../etc".to_string(),
3512 group_name: None,
3513 timestamp_ms: 0,
3514 };
3515 let err = alice.replace_session_with_welcome(&welcome).unwrap_err();
3516 assert!(matches!(err, MlsError::InvalidUserId(_)));
3517 }
3518
3519 fn create_test_session() -> (MlsManager, MlsManager) {
3521 let alice = create_test_manager("alice");
3522 let bob = create_test_manager("bob");
3523 let bob_kp = bob.generate_key_package().unwrap();
3524 alice
3525 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3526 .unwrap();
3527 let welcome = alice.create_session(&addr("bob")).unwrap();
3528 bob.join_session(&welcome).unwrap();
3529 (alice, bob)
3530 }
3531
3532 use crate::storage::{StorageError, StorageResult};
3533
3534 struct StampDeleteFails(InMemoryStorage);
3536
3537 impl MlsStorage for StampDeleteFails {
3538 fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> StorageResult<()> {
3539 self.0.store(key_type, key_id, data)
3540 }
3541 fn load(&self, key_type: &str, key_id: &str) -> StorageResult<Option<Vec<u8>>> {
3542 self.0.load(key_type, key_id)
3543 }
3544 fn delete(&self, key_type: &str, key_id: &str) -> StorageResult<()> {
3545 if key_type == StorageKeyType::GroupMetadata.as_str() {
3546 return Err(StorageError::DeleteFailed("refused".to_string()));
3547 }
3548 self.0.delete(key_type, key_id)
3549 }
3550 fn list_keys(&self, key_type: &str) -> StorageResult<Vec<String>> {
3551 self.0.list_keys(key_type)
3552 }
3553 }
3554
3555 #[test]
3559 fn a_failed_delete_session_leaves_the_session_in_place() {
3560 let storage: Arc<dyn MlsStorage> = Arc::new(StampDeleteFails(InMemoryStorage::new()));
3561 let (keys, address) = test_identity("alice");
3562 seed_identity(&storage, &keys);
3563 let alice = MlsManager::new(address.to_string(), storage).unwrap();
3564 let bob = create_test_manager("bob");
3565 let bob_kp = bob.generate_key_package().unwrap();
3566 alice
3567 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3568 .unwrap();
3569 alice.create_session(&addr("bob")).unwrap();
3570
3571 assert!(alice.delete_session(&addr("bob")).is_err());
3572 assert!(alice.has_session(&addr("bob")).unwrap());
3573 assert!(alice
3574 .session_established_at_ms(&addr("bob"))
3575 .unwrap()
3576 .is_some());
3577 }
3578
3579 #[test]
3580 fn test_session_decrypt_rejects_spoofed_sender() {
3581 let (alice, bob) = create_test_session();
3582
3583 let ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
3589 let err = bob.decrypt_from_user(&ct, &addr("mallory")).unwrap_err();
3590 assert!(
3591 matches!(err, MlsError::SessionIdentityMismatch { .. }),
3592 "spoofed sender must be rejected by the slot binding, got {:?}",
3593 err
3594 );
3595
3596 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
3601 assert_eq!(pt.as_deref(), Some(&b"hello"[..]));
3602
3603 }
3607
3608 #[test]
3609 fn test_epoch_fork_classifies_as_session_desync() {
3610 let (alice, bob) = create_test_session();
3615
3616 let group_id = slot("alice", "bob");
3619 alice.update_keys(&group_id).unwrap();
3620
3621 let ct = alice.encrypt_for_user(&addr("bob"), b"after-fork").unwrap();
3624 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
3625 assert!(
3626 matches!(err, MlsError::SessionDesync(_)),
3627 "epoch fork must classify as SessionDesync, got {:?}",
3628 err
3629 );
3630 }
3631
3632 fn push_varint(out: &mut Vec<u8>, len: usize) {
3645 match len {
3646 0..=63 => out.push(len as u8),
3647 64..=16_383 => {
3648 out.push(0x40 | (len >> 8) as u8);
3649 out.push((len & 0xff) as u8);
3650 }
3651 _ => panic!("test payloads stay under the 2-byte varint ceiling"),
3652 }
3653 }
3654
3655 fn forge_private_message(group_id: &str, epoch: u64) -> Vec<u8> {
3656 let mut out = Vec::new();
3657 out.extend_from_slice(&1u16.to_be_bytes()); out.extend_from_slice(&2u16.to_be_bytes()); push_varint(&mut out, group_id.len());
3660 out.extend_from_slice(group_id.as_bytes());
3661 out.extend_from_slice(&epoch.to_be_bytes());
3662 out.push(1); out.push(0); out.push(8); out.extend_from_slice(&[0u8; 8]);
3666 out.push(16); out.extend_from_slice(&[0u8; 16]);
3668 out
3669 }
3670
3671 #[test]
3672 fn test_forged_frame_reaches_session_desync_without_any_key_material() {
3673 let (_alice, bob) = create_test_session();
3686
3687 let forged = EncryptedMessage {
3688 group_id: slot("alice", "bob"),
3689 message_type: MlsMessageType::Application,
3690 epoch: 9_999,
3691 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
3692 sender_id: addr("alice"),
3693 timestamp_ms: 0,
3694 };
3695
3696 let err = bob.decrypt_from_user(&forged, &addr("alice")).unwrap_err();
3697 assert!(
3698 matches!(err, MlsError::SessionDesync(_)),
3699 "a forged future-epoch frame reaches the desync classification pre-AEAD, got {:?}",
3700 err
3701 );
3702 }
3703
3704 #[test]
3705 fn test_forged_frame_naming_a_foreign_slot_is_rejected_before_desync() {
3706 let (_alice, bob) = create_test_session();
3712
3713 let forged = EncryptedMessage {
3714 group_id: slot("alice", "bob"),
3715 message_type: MlsMessageType::Application,
3716 epoch: 9_999,
3717 ciphertext: forge_private_message(slot("alice", "bob").as_str(), 9_999),
3718 sender_id: addr("mallory"),
3719 timestamp_ms: 0,
3720 };
3721
3722 let err = bob
3723 .decrypt_from_user(&forged, &addr("mallory"))
3724 .unwrap_err();
3725 assert!(
3726 matches!(err, MlsError::SessionIdentityMismatch { .. }),
3727 "a frame naming another pair's slot must not classify as recoverable, got {:?}",
3728 err
3729 );
3730 }
3731
3732 #[test]
3733 fn test_corrupt_ciphertext_is_not_classified_as_session_desync() {
3734 let (alice, bob) = create_test_session();
3740 let mut ct = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
3741 for b in ct.ciphertext.iter_mut() {
3743 *b ^= 0xFF;
3744 }
3745 let err = bob.decrypt_from_user(&ct, &addr("alice")).unwrap_err();
3746 assert!(
3747 !matches!(err, MlsError::SessionDesync(_)),
3748 "corrupt ciphertext must not be treated as a recoverable desync, got {:?}",
3749 err
3750 );
3751 }
3752
3753 #[test]
3754 fn test_join_session_binds_slot_to_inviter() {
3755 let bob = create_test_manager("bob");
3762
3763 let hijack = WelcomeMessage {
3766 group_id: slot("alice", "bob"),
3767 welcome_data: vec![], inviter_id: addr("mallory"),
3769 group_name: None,
3770 timestamp_ms: 0,
3771 };
3772
3773 let err = bob.join_session(&hijack).unwrap_err();
3775 assert!(
3776 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3777 "join_session: expected WelcomeIdentityMismatch, got {:?}",
3778 err
3779 );
3780 let err = bob.replace_session_with_welcome(&hijack).unwrap_err();
3781 assert!(
3782 matches!(err, MlsError::WelcomeIdentityMismatch { .. }),
3783 "replace_session_with_welcome: expected WelcomeIdentityMismatch, got {:?}",
3784 err
3785 );
3786
3787 assert!(!bob.has_session(&addr("alice")).unwrap());
3789 assert!(!bob.has_session(&addr("mallory")).unwrap());
3790
3791 let bob_kp = bob.generate_key_package().unwrap();
3795 let mallory = create_test_manager("mallory");
3796 mallory
3797 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3798 .unwrap();
3799 let legit = mallory.create_session(&addr("bob")).unwrap();
3800 bob.join_session(&legit).unwrap();
3801 assert!(bob.has_session(&addr("mallory")).unwrap());
3802 }
3803
3804 #[test]
3805 fn test_join_group_rejects_session_namespace() {
3806 let bob = create_test_manager("bob");
3814
3815 let squat = WelcomeMessage {
3816 group_id: slot("alice", "bob"),
3817 welcome_data: vec![], inviter_id: addr("mallory"),
3819 group_name: None,
3820 timestamp_ms: 0,
3821 };
3822
3823 let err = bob.join_group(&squat).unwrap_err();
3824 assert!(
3825 matches!(err, MlsError::ReservedSessionNamespace { .. }),
3826 "join_group: expected ReservedSessionNamespace, got {:?}",
3827 err
3828 );
3829
3830 assert!(!bob.has_session(&addr("alice")).unwrap());
3832
3833 let legit_ns = WelcomeMessage {
3837 group_id: GroupId::new("group:0bd6e5f2-3a70-4a4e-9c3f-1c1f2a3b4c5d").unwrap(),
3838 welcome_data: vec![],
3839 inviter_id: addr("mallory"),
3840 group_name: None,
3841 timestamp_ms: 0,
3842 };
3843 let err = bob.join_group(&legit_ns).unwrap_err();
3844 assert!(
3845 !matches!(err, MlsError::ReservedSessionNamespace { .. }),
3846 "a group:-namespaced Welcome must not trip the session-namespace guard, got {:?}",
3847 err
3848 );
3849 }
3850
3851 #[test]
3852 fn test_join_group_rejects_embedded_group_id_mismatch() {
3853 let bob = create_test_manager("bob");
3862 let mallory = create_test_manager("mallory");
3863
3864 let bob_kp = bob.generate_key_package().unwrap();
3866 let embedded = GroupId::new("group:11111111-1111-4111-8111-111111111111").unwrap();
3867 let cred = mallory.get_credential().unwrap();
3868 let signer = mallory.get_signer().unwrap();
3869 mallory
3870 .group_manager
3871 .create_group(&embedded, &cred, &signer)
3872 .unwrap();
3873 let (welcome, _commit) = mallory
3874 .add_group_member(&embedded, &addr("bob"), &bob_kp.key_package_data)
3875 .unwrap();
3876 assert_eq!(welcome.group_id, embedded);
3877
3878 let wire = GroupId::new("group:22222222-2222-4222-8222-222222222222").unwrap();
3882 let tampered = WelcomeMessage {
3883 group_id: wire.clone(),
3884 ..welcome
3885 };
3886
3887 let err = bob.join_group(&tampered).unwrap_err();
3888 assert!(
3889 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3890 "expected WelcomeGroupIdMismatch, got {:?}",
3891 err
3892 );
3893
3894 assert!(bob.group_manager.load_group(&wire).unwrap().is_none());
3896 assert!(bob.group_manager.load_group(&embedded).unwrap().is_none());
3897 }
3898
3899 #[test]
3900 fn test_welcome_embedded_id_cannot_hijack_session_slot() {
3901 let alice = create_test_manager("alice");
3910 let bob = create_test_manager("bob");
3911 let bob_kp = bob.generate_key_package().unwrap();
3912 alice
3913 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3914 .unwrap();
3915 let welcome = alice.create_session(&addr("bob")).unwrap();
3916 bob.join_session(&welcome).unwrap();
3917 assert!(bob.has_session(&addr("alice")).unwrap());
3918
3919 let mallory = create_test_manager("mallory");
3921 let bob_kp2 = bob.generate_key_package().unwrap();
3922 let squat = slot("alice", "bob");
3923 let cred = mallory.get_credential().unwrap();
3924 let signer = mallory.get_signer().unwrap();
3925 mallory
3926 .group_manager
3927 .create_group(&squat, &cred, &signer)
3928 .unwrap();
3929 let (mal_welcome, _commit) = mallory
3930 .add_group_member(&squat, &addr("bob"), &bob_kp2.key_package_data)
3931 .unwrap();
3932
3933 let attack = WelcomeMessage {
3935 group_id: slot("bob", "mallory"),
3936 welcome_data: mal_welcome.welcome_data,
3937 inviter_id: addr("mallory"),
3938 group_name: None,
3939 timestamp_ms: 0,
3940 };
3941
3942 let err = bob.join_session(&attack).unwrap_err();
3943 assert!(
3944 matches!(err, MlsError::WelcomeGroupIdMismatch { .. }),
3945 "expected WelcomeGroupIdMismatch, got {:?}",
3946 err
3947 );
3948
3949 assert!(bob.has_session(&addr("alice")).unwrap());
3951 let ct = bob
3952 .encrypt_for_user(&addr("alice"), b"still private")
3953 .unwrap();
3954 let pt = alice.decrypt_from_user(&ct, &addr("bob")).unwrap();
3955 assert_eq!(pt.as_deref(), Some(&b"still private"[..]));
3956 }
3957
3958 fn create_test_group_with_bob() -> (MlsManager, MlsManager, GroupId) {
3961 let alice = create_test_manager("alice");
3962 let bob = create_test_manager("bob");
3963 let bob_kp = bob.generate_key_package().unwrap();
3964 alice
3965 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
3966 .unwrap();
3967 let info = alice.create_group("Test Group").unwrap();
3968 let gid = info.group_id.clone();
3969 let (welcome, _commit) = alice
3970 .add_group_member(&gid, &addr("bob"), &bob_kp.key_package_data)
3971 .unwrap();
3972 bob.join_group(&welcome).unwrap();
3973 (alice, bob, gid)
3974 }
3975
3976 #[test]
3977 fn test_group_decrypt_rejects_spoofed_sender() {
3978 let (alice, bob, gid) = create_test_group_with_bob();
3979
3980 let ct = alice.encrypt_for_group(&gid, b"group message").unwrap();
3981 let err = bob.decrypt_from_group(&ct, &addr("mallory")).unwrap_err();
3982 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
3983
3984 let ct2 = alice.encrypt_for_group(&gid, b"another one").unwrap();
3985 let pt = bob.decrypt_from_group(&ct2, &addr("alice")).unwrap();
3986 assert_eq!(pt.as_deref(), Some(&b"another one"[..]));
3987 }
3988
3989 #[test]
3990 fn test_group_commit_with_spoofed_sender_rejected_before_merge() {
3991 let (alice, bob, gid) = create_test_group_with_bob();
3992
3993 let commit = alice.update_keys(&gid).unwrap();
3997 let epoch_before = bob.get_group_info(&gid).unwrap().unwrap().epoch;
3998
3999 let err = bob
4000 .decrypt_from_group(&commit, &addr("mallory"))
4001 .unwrap_err();
4002 assert!(matches!(err, MlsError::SenderIdentityMismatch { .. }));
4003
4004 let epoch_after = bob.get_group_info(&gid).unwrap().unwrap().epoch;
4005 assert_eq!(
4006 epoch_before, epoch_after,
4007 "spoofed commit must not advance group state"
4008 );
4009 }
4010
4011 #[test]
4012 fn test_no_session_initially() {
4013 let manager = create_test_manager("alice");
4014 assert!(!manager.has_session(&addr("bob")).unwrap());
4015 assert!(manager.list_sessions().unwrap().is_empty());
4016 }
4017
4018 #[test]
4019 fn test_no_groups_initially() {
4020 let manager = create_test_manager("alice");
4021 assert!(manager.list_groups().unwrap().is_empty());
4022 }
4023
4024 #[test]
4025 fn test_group_creation() {
4026 let manager = create_test_manager("alice");
4027 let info = manager.create_group("Test Group").unwrap();
4028
4029 assert_eq!(info.name, Some("Test Group".to_string()));
4030 assert!(!info.is_session);
4031 assert_eq!(info.members.len(), 1);
4032
4033 let groups = manager.list_groups().unwrap();
4034 assert_eq!(groups.len(), 1);
4035 }
4036
4037 #[test]
4048 fn test_both_create_adopt_is_non_destructive_on_welcome_retransmit() {
4049 let alice = create_test_manager("alice");
4051 let bob = create_test_manager("bob");
4052
4053 let alice_kp = alice.generate_key_package().unwrap();
4055 let bob_kp = bob.generate_key_package().unwrap();
4056 alice
4057 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4058 .unwrap();
4059 bob.import_key_package(&addr("alice"), &alice_kp.key_package_data)
4060 .unwrap();
4061
4062 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());
4066
4067 bob.join_session(&alice_welcome).unwrap();
4070 assert!(bob.has_session(&addr("alice")).unwrap());
4071
4072 let ct = alice
4074 .encrypt_for_user(&addr("bob"), b"hello over the converged group")
4075 .unwrap();
4076 let pt = bob.decrypt_from_user(&ct, &addr("alice")).unwrap();
4077 assert_eq!(pt.as_deref(), Some(&b"hello over the converged group"[..]));
4078
4079 let retransmit = bob.join_session(&alice_welcome);
4082 assert!(
4083 retransmit.is_err(),
4084 "re-staging a consumed key package should fail"
4085 );
4086
4087 assert!(
4090 bob.has_session(&addr("alice")).unwrap(),
4091 "duplicate Welcome must not brick the converged session"
4092 );
4093
4094 let ct2 = alice
4096 .encrypt_for_user(&addr("bob"), b"still converged after retransmit")
4097 .unwrap();
4098 let pt2 = bob.decrypt_from_user(&ct2, &addr("alice")).unwrap();
4099 assert_eq!(
4100 pt2.as_deref(),
4101 Some(&b"still converged after retransmit"[..])
4102 );
4103 }
4104
4105 #[test]
4111 fn test_out_of_order_decryption_within_sender_ratchet_tolerance() {
4112 let alice = create_test_manager("alice");
4113 let bob = create_test_manager("bob");
4114
4115 let bob_kp = bob.generate_key_package().unwrap();
4116 alice
4117 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4118 .unwrap();
4119 let welcome = alice.create_session(&addr("bob")).unwrap();
4120 bob.join_session(&welcome).unwrap();
4121
4122 let ciphertexts: Vec<_> = (0..40)
4123 .map(|i| {
4124 alice
4125 .encrypt_for_user(&addr("bob"), format!("chunk {}", i).as_bytes())
4126 .unwrap()
4127 })
4128 .collect();
4129
4130 let pt = bob
4133 .decrypt_from_user(&ciphertexts[39], &addr("alice"))
4134 .unwrap();
4135 assert_eq!(pt.as_deref(), Some(&b"chunk 39"[..]));
4136
4137 let pt = bob
4140 .decrypt_from_user(&ciphertexts[10], &addr("alice"))
4141 .unwrap();
4142 assert_eq!(pt.as_deref(), Some(&b"chunk 10"[..]));
4143
4144 let res = bob.decrypt_from_user(&ciphertexts[0], &addr("alice"));
4147 assert!(
4148 !matches!(res, Ok(Some(_))),
4149 "generation beyond the tolerance must not decrypt"
4150 );
4151 }
4152
4153 #[test]
4164 fn test_a_receiver_past_the_forward_window_classifies_as_session_desync() {
4165 let (alice, bob) = create_test_session();
4166
4167 let mut last = None;
4169 for i in 0..100 {
4170 last = Some(
4171 alice
4172 .encrypt_for_user(&addr("bob"), format!("in window {i}").as_bytes())
4173 .unwrap(),
4174 );
4175 }
4176 let pt = bob
4177 .decrypt_from_user(&last.unwrap(), &addr("alice"))
4178 .unwrap();
4179 assert_eq!(pt.as_deref(), Some(&b"in window 99"[..]));
4180
4181 let gap = crate::group::SENDER_RATCHET_MAXIMUM_FORWARD_DISTANCE + 1;
4183 for _ in 0..gap {
4184 alice.encrypt_for_user(&addr("bob"), b"lost").unwrap();
4185 }
4186 let ahead = alice.encrypt_for_user(&addr("bob"), b"too far").unwrap();
4187 let err = bob.decrypt_from_user(&ahead, &addr("alice")).unwrap_err();
4188 assert!(
4189 matches!(err, MlsError::SessionDesync(_)),
4190 "a frame past the forward window must classify as SessionDesync, got {err:?}"
4191 );
4192 }
4193
4194 #[test]
4199 fn test_a_frame_past_the_out_of_order_tolerance_is_not_a_desync() {
4200 let (alice, bob) = create_test_session();
4201 let old = alice.encrypt_for_user(&addr("bob"), b"old").unwrap();
4202 let mut newest = None;
4203 for _ in 0..=crate::group::SENDER_RATCHET_OUT_OF_ORDER_TOLERANCE + 1 {
4204 newest = Some(alice.encrypt_for_user(&addr("bob"), b"newer").unwrap());
4205 }
4206 bob.decrypt_from_user(&newest.unwrap(), &addr("alice"))
4207 .unwrap();
4208 let err = bob.decrypt_from_user(&old, &addr("alice")).unwrap_err();
4209 assert!(
4210 matches!(err, MlsError::Decryption(_)),
4211 "a frame behind the tolerance must stay Decryption, got {err:?}"
4212 );
4213 assert_eq!(err.decryption_detail(), Some("too_distant_in_the_past"));
4214 }
4215
4216 #[test]
4223 fn test_a_split_session_fails_as_aead_at_the_same_epoch() {
4224 let alice = create_test_manager("alice");
4225 let bob = create_test_manager("bob");
4226 let alice_kp = alice.generate_key_package().unwrap();
4227 let bob_kp = bob.generate_key_package().unwrap();
4228 alice
4229 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4230 .unwrap();
4231 bob.import_key_package(&addr("alice"), &alice_kp.key_package_data)
4232 .unwrap();
4233 alice.create_session(&addr("bob")).unwrap();
4234 bob.create_session(&addr("alice")).unwrap();
4235
4236 let frame = alice.encrypt_for_user(&addr("bob"), b"hello").unwrap();
4237 let err = bob.decrypt_from_user(&frame, &addr("alice")).unwrap_err();
4238 assert!(
4239 matches!(err, MlsError::Decryption(_)),
4240 "a split session must fail as Decryption, got {err:?}"
4241 );
4242 assert_eq!(err.decryption_detail(), Some("aead"));
4243 }
4244
4245 #[test]
4247 fn test_a_welcome_targets_only_the_package_it_was_built_on() {
4248 let alice = create_test_manager("alice");
4249 let bob = create_test_manager("bob");
4250 let bob_kp = bob.generate_key_package().unwrap();
4251 let other_kp = bob.generate_key_package().unwrap();
4252 alice
4253 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4254 .unwrap();
4255 let welcome = alice.create_session(&addr("bob")).unwrap();
4256 assert!(alice
4257 .welcome_targets_key_package(&welcome, &bob_kp.key_package_data)
4258 .unwrap());
4259 assert!(!alice
4260 .welcome_targets_key_package(&welcome, &other_kp.key_package_data)
4261 .unwrap());
4262 }
4263
4264 #[test]
4268 fn test_one_session_corroborates_on_both_sides() {
4269 let (alice, bob) = create_test_session();
4270 let nonce = [3u8; 16];
4271 let ours = alice.session_corroboration(&addr("bob"), &nonce).unwrap();
4272 let theirs = bob.session_corroboration(&addr("alice"), &nonce).unwrap();
4273 assert!(ours.is_some());
4274 assert_eq!(ours, theirs);
4275 let other = alice
4276 .session_corroboration(&addr("bob"), &[4u8; 16])
4277 .unwrap();
4278 assert_ne!(ours.unwrap().1, other.unwrap().1);
4279 assert_eq!(
4280 alice.session_corroboration(&addr("carol"), &nonce).unwrap(),
4281 None
4282 );
4283 }
4284
4285 #[test]
4288 fn test_a_split_session_agrees_on_the_epoch_and_not_the_tag() {
4289 let alice = create_test_manager("alice");
4290 let bob = create_test_manager("bob");
4291 let alice_kp = alice.generate_key_package().unwrap();
4292 let bob_kp = bob.generate_key_package().unwrap();
4293 alice
4294 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4295 .unwrap();
4296 bob.import_key_package(&addr("alice"), &alice_kp.key_package_data)
4297 .unwrap();
4298 alice.create_session(&addr("bob")).unwrap();
4299 bob.create_session(&addr("alice")).unwrap();
4300
4301 let nonce = [3u8; 16];
4302 let (alice_epoch, alice_tag) = alice
4303 .session_corroboration(&addr("bob"), &nonce)
4304 .unwrap()
4305 .unwrap();
4306 let (bob_epoch, bob_tag) = bob
4307 .session_corroboration(&addr("alice"), &nonce)
4308 .unwrap()
4309 .unwrap();
4310 assert_eq!(alice_epoch, bob_epoch);
4311 assert_ne!(alice_tag, bob_tag);
4312 }
4313
4314 #[test]
4318 fn test_unacknowledged_seals_track_seals_acks_and_session_rebuilds() {
4319 let (alice, _bob) = create_test_session();
4320 let bob = addr("bob");
4321 assert_eq!(alice.unacknowledged_seals(&bob), 0);
4322
4323 alice.encrypt_for_user(&bob, b"one").unwrap();
4324 let first = alice.last_seal_mark(&bob).unwrap();
4325 alice.encrypt_for_user(&bob, b"two").unwrap();
4326 alice.encrypt_for_user(&bob, b"three").unwrap();
4327 assert_eq!(alice.unacknowledged_seals(&bob), 3);
4328
4329 alice.note_acknowledged_seal(&bob, first);
4330 assert_eq!(alice.unacknowledged_seals(&bob), 2);
4331 alice.note_acknowledged_seal(&bob, crate::session::SealMark { index: 0, ..first });
4333 assert_eq!(alice.unacknowledged_seals(&bob), 2);
4334
4335 alice.delete_session(&bob).unwrap();
4336 assert_eq!(alice.unacknowledged_seals(&bob), 0);
4337 assert!(alice.last_seal_mark(&bob).is_none());
4338 alice.note_acknowledged_seal(&bob, crate::session::SealMark { index: 99, ..first });
4341 assert_eq!(alice.unacknowledged_seals(&bob), 0);
4342 }
4343
4344 const RFC8032_TV1_PK: [u8; 32] = [
4346 0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07,
4347 0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07,
4348 0x51, 0x1a,
4349 ];
4350
4351 const RFC8032_TV1_ADDRESS: &str = "off1qysluvwl5922yctzd0u9gpr06gn3k7ldfvgtwgvn";
4356
4357 #[test]
4365 fn derive_address_matches_the_pinned_vector() {
4366 let address = MlsManager::derive_address(&RFC8032_TV1_PK).expect("32-byte key derives");
4367 assert_eq!(address.to_string(), RFC8032_TV1_ADDRESS);
4368 assert_eq!(
4371 RFC8032_TV1_ADDRESS.parse::<Address>().expect("parses"),
4372 address
4373 );
4374 }
4375
4376 #[test]
4377 fn derive_address_rejects_wrong_key_lengths() {
4378 for len in [0usize, 31, 33, 64] {
4379 let key = vec![0u8; len];
4380 assert!(
4381 matches!(
4382 MlsManager::derive_address(&key),
4383 Err(MlsError::InvalidPublicKey(_))
4384 ),
4385 "{len}-byte key must be rejected"
4386 );
4387 }
4388 }
4389
4390 #[test]
4391 fn deprecated_derive_agrees_with_derive_address() {
4392 #[allow(deprecated)]
4395 let legacy = MlsManager::derive_user_id_from_public_key(&RFC8032_TV1_PK);
4396 assert_eq!(legacy, RFC8032_TV1_ADDRESS);
4397 assert_eq!(
4398 legacy,
4399 MlsManager::derive_address(&RFC8032_TV1_PK)
4400 .expect("derives")
4401 .to_string()
4402 );
4403 }
4404
4405 #[test]
4406 fn load_or_create_identity_mints_once_and_is_idempotent() {
4407 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4408
4409 let (first_keys, first_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4410 let (second_keys, second_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4411
4412 assert_eq!(first_keys.public(), second_keys.public());
4414 assert_eq!(first_address, second_address);
4415 assert_eq!(
4416 MlsManager::derive_address(first_keys.public()).unwrap(),
4417 first_address,
4418 "the returned address must be the stored key's derivation"
4419 );
4420 }
4421
4422 #[test]
4423 fn load_or_create_identity_adopts_an_existing_key() {
4424 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4425 let (keys, address) = test_identity("alice");
4426 seed_identity(&storage, &keys);
4427
4428 let (loaded, loaded_address) = MlsManager::load_or_create_identity(&storage).unwrap();
4429
4430 assert_eq!(loaded.public(), keys.public());
4431 assert_eq!(loaded_address, address);
4432 }
4433
4434 #[test]
4435 fn manager_bootstrapped_at_its_derived_address_loads_that_identity() {
4436 let (manager, address) = create_addressed_manager("alice");
4437
4438 assert_eq!(manager.user_id(), address.to_string());
4439 assert_eq!(
4440 MlsManager::derive_address(&manager.get_identity_public_key().unwrap()).unwrap(),
4441 address,
4442 "the manager's identity key must derive to the address it claims"
4443 );
4444 }
4445
4446 #[test]
4451 fn manager_refuses_an_address_whose_key_is_not_the_stored_one() {
4452 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4453 let (alice_keys, alice_address) = test_identity("alice");
4454 let (_, bob_address) = test_identity("bob");
4455 seed_identity(&storage, &alice_keys);
4456
4457 let Err(err) = MlsManager::new(bob_address.to_string(), storage) else {
4458 panic!("a manager must not claim an address it holds no key for");
4459 };
4460
4461 match err {
4462 MlsError::IdentityAddressMismatch { expected, derived } => {
4463 assert_eq!(expected, bob_address.to_string());
4464 assert_eq!(derived, alice_address.to_string());
4465 }
4466 other => panic!("expected IdentityAddressMismatch, got {other:?}"),
4467 }
4468 }
4469
4470 #[test]
4474 fn manager_refuses_to_claim_an_address_with_no_stored_identity() {
4475 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4476 let (_, address) = test_identity("alice");
4477
4478 let Err(err) = MlsManager::new(address.to_string(), storage.clone()) else {
4479 panic!("a manager must not mint a key and claim a different address");
4480 };
4481 assert!(matches!(err, MlsError::IdentityAddressMismatch { .. }));
4482
4483 assert!(
4484 storage
4485 .load(StorageKeyType::Identity.as_str(), "key_pair")
4486 .unwrap()
4487 .is_none(),
4488 "a refused bootstrap must not leave a key behind"
4489 );
4490 }
4491
4492 #[test]
4504 fn manager_with_a_non_address_id_skips_the_binding_check() {
4505 let manager = MlsManager::new("alice", Arc::new(InMemoryStorage::new())).unwrap();
4506 assert_eq!(manager.user_id(), "alice");
4507 }
4508
4509 struct Impostor {
4522 provider: MlsProvider,
4523 keys: SignatureKeyPair,
4524 key_package: Vec<u8>,
4525 group: Option<MlsGroup>,
4526 }
4527
4528 impl Impostor {
4529 fn claiming(claimed: &str, key_label: &str) -> Self {
4532 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
4533 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
4534 let (keys, _) = test_identity(key_label);
4535 keys.store(provider.storage()).unwrap();
4536
4537 let credential = CredentialWithKey {
4538 credential: Credential::new(CredentialType::Basic, claimed.as_bytes().to_vec()),
4539 signature_key: keys.public().into(),
4540 };
4541 let key_package = KeyPackage::builder()
4542 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
4543 .unwrap()
4544 .key_package()
4545 .tls_serialize_detached()
4546 .unwrap();
4547
4548 Self {
4549 provider,
4550 keys,
4551 key_package,
4552 group: None,
4553 }
4554 }
4555
4556 fn join(&mut self, welcome: &MlsMessageOut) {
4558 let bytes = welcome.tls_serialize_detached().unwrap();
4559 let welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
4560 .unwrap()
4561 .extract()
4562 {
4563 MlsMessageBodyIn::Welcome(w) => w,
4564 _ => panic!("not a welcome"),
4565 };
4566 let cfg = MlsGroupJoinConfig::builder()
4567 .use_ratchet_tree_extension(true)
4568 .build();
4569 self.group = Some(
4570 StagedWelcome::new_from_welcome(&self.provider, &cfg, welcome, None)
4571 .unwrap()
4572 .into_group(&self.provider)
4573 .unwrap(),
4574 );
4575 }
4576
4577 fn merge_commit(&mut self, commit_bytes: &[u8]) {
4583 let group = self.group.as_mut().expect("join first");
4584 let message = MlsMessageIn::tls_deserialize_exact(commit_bytes)
4585 .unwrap()
4586 .try_into_protocol_message()
4587 .unwrap();
4588 let processed = group.process_message(&self.provider, message).unwrap();
4589 match processed.into_content() {
4590 ProcessedMessageContent::StagedCommitMessage(staged) => {
4591 group.merge_staged_commit(&self.provider, *staged).unwrap();
4592 }
4593 _ => panic!("not a commit"),
4594 }
4595 }
4596
4597 fn encrypt(&mut self, plaintext: &[u8]) -> Vec<u8> {
4599 self.group
4600 .as_mut()
4601 .expect("join first")
4602 .create_message(&self.provider, &self.keys, plaintext)
4603 .unwrap()
4604 .tls_serialize_detached()
4605 .unwrap()
4606 }
4607 }
4608
4609 fn add_member_bypassing_checks(
4617 manager: &MlsManager,
4618 group_id: &GroupId,
4619 key_package_bytes: &[u8],
4620 ) -> (MlsMessageOut, MlsMessageOut) {
4621 let mut group = manager
4622 .group_manager
4623 .load_group(group_id)
4624 .unwrap()
4625 .expect("group exists");
4626 let key_package = KeyPackageIn::tls_deserialize_exact(key_package_bytes)
4627 .unwrap()
4628 .validate(
4629 manager.group_manager.provider().crypto(),
4630 ProtocolVersion::Mls10,
4631 )
4632 .unwrap();
4633 let signer = manager.get_signer().unwrap();
4634 let (commit, welcome) = manager
4635 .group_manager
4636 .add_member(&mut group, key_package, &signer)
4637 .unwrap();
4638 manager.group_manager.save_group(group_id, &group).unwrap();
4639 (commit, welcome)
4640 }
4641
4642 fn commit_message(
4643 group_id: &GroupId,
4644 commit: &MlsMessageOut,
4645 sender: &str,
4646 ) -> EncryptedMessage {
4647 EncryptedMessage {
4648 group_id: group_id.clone(),
4649 message_type: MlsMessageType::Commit,
4650 epoch: 0,
4651 ciphertext: commit.tls_serialize_detached().unwrap(),
4652 sender_id: sender.to_string(),
4653 timestamp_ms: 0,
4654 }
4655 }
4656
4657 fn app_message(group_id: &GroupId, ciphertext: Vec<u8>, sender: &str) -> EncryptedMessage {
4658 EncryptedMessage {
4659 group_id: group_id.clone(),
4660 message_type: MlsMessageType::Application,
4661 epoch: 0,
4662 ciphertext,
4663 sender_id: sender.to_string(),
4664 timestamp_ms: 0,
4665 }
4666 }
4667
4668 #[test]
4676 fn test_committed_forged_leaf_is_refused_and_cannot_speak_as_its_victim() {
4677 let mallory = create_test_manager("mallory");
4678 let alice = create_test_manager("alice");
4679 let alice_kp = alice.generate_key_package().unwrap();
4680
4681 let gid = mallory.create_group("Team").unwrap().group_id;
4682 let (welcome, _) = mallory
4683 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4684 .unwrap();
4685 alice.join_group(&welcome).unwrap();
4686 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
4687
4688 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4690 assert!(matches!(
4691 mallory
4692 .add_group_member(&gid, &addr("bob"), &impostor.key_package)
4693 .unwrap_err(),
4694 MlsError::KeyPackageAddressMismatch { .. }
4695 ));
4696 let (commit, forged_welcome) =
4697 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4698
4699 let err = alice
4701 .decrypt_from_group(
4702 &commit_message(&gid, &commit, &addr("mallory")),
4703 &addr("mallory"),
4704 )
4705 .unwrap_err();
4706 assert!(
4707 matches!(
4708 err,
4709 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4710 if *claimed == addr("bob") && *context == crate::error::LeafSource::CommitAdd
4711 ),
4712 "expected a CommitAdd binding refusal, got {err:?}"
4713 );
4714
4715 let after = alice.get_group_info(&gid).unwrap().unwrap();
4717 assert_eq!(
4718 after.epoch, epoch_before,
4719 "a refused commit advanced the epoch"
4720 );
4721 assert!(
4722 !after.members.contains(&addr("bob")),
4723 "roster: {:?}",
4724 after.members
4725 );
4726
4727 impostor.join(&forged_welcome);
4735 let forged = impostor.encrypt(b"send me the recovery phrase");
4736 assert!(
4737 alice
4738 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
4739 .is_err(),
4740 "a forged leaf's message was delivered"
4741 );
4742 }
4743
4744 #[test]
4748 fn test_welcome_carrying_a_forged_leaf_is_refused() {
4749 let mallory = create_test_manager("mallory");
4750 let alice = create_test_manager("alice");
4751
4752 let gid = mallory.create_group("Team").unwrap().group_id;
4753 let impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4754 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4755
4756 let alice_kp = alice.generate_key_package().unwrap();
4758 let (welcome, _) = mallory
4759 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4760 .unwrap();
4761
4762 let err = alice.join_group(&welcome).unwrap_err();
4763 assert!(
4764 matches!(
4765 err,
4766 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4767 if *claimed == addr("bob") && *context == crate::error::LeafSource::WelcomeTree
4768 ),
4769 "expected a WelcomeTree binding refusal, got {err:?}"
4770 );
4771
4772 assert!(alice.get_group_info(&gid).unwrap().is_none());
4774 }
4775
4776 #[test]
4781 fn test_session_welcome_carrying_a_forged_leaf_is_refused_non_destructively() {
4782 let alice = create_test_manager("alice");
4783 let bob = create_test_manager("bob");
4784 let mallory = create_test_manager("mallory");
4785
4786 let bob_kp = bob.generate_key_package().unwrap();
4788 alice
4789 .import_key_package(&addr("bob"), &bob_kp.key_package_data)
4790 .unwrap();
4791 bob.join_session(&alice.create_session(&addr("bob")).unwrap())
4792 .unwrap();
4793 assert!(bob.has_session(&addr("alice")).unwrap());
4794
4795 let bob_kp2 = bob.generate_key_package().unwrap();
4798 let slot_id = slot("bob", "mallory");
4799 let cred = mallory.get_credential().unwrap();
4800 let signer = mallory.get_signer().unwrap();
4801 mallory
4802 .group_manager
4803 .create_group(&slot_id, &cred, &signer)
4804 .unwrap();
4805 let impostor = Impostor::claiming(&addr("carol"), "mallorys-second-key");
4806 add_member_bypassing_checks(&mallory, &slot_id, &impostor.key_package);
4807 let (welcome, _) = mallory
4808 .add_group_member(&slot_id, &addr("bob"), &bob_kp2.key_package_data)
4809 .unwrap();
4810
4811 let err = bob.join_session(&welcome).unwrap_err();
4812 assert!(
4813 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
4814 if *context == crate::error::LeafSource::WelcomeTree),
4815 "expected a WelcomeTree binding refusal, got {err:?}"
4816 );
4817
4818 assert!(bob.has_session(&addr("alice")).unwrap());
4820 let ct = bob
4821 .encrypt_for_user(&addr("alice"), b"still private")
4822 .unwrap();
4823 assert_eq!(
4824 alice
4825 .decrypt_from_user(&ct, &addr("bob"))
4826 .unwrap()
4827 .as_deref(),
4828 Some(&b"still private"[..])
4829 );
4830 }
4831
4832 #[test]
4840 fn test_forged_leaf_seated_behind_the_gates_still_cannot_speak() {
4841 let mallory = create_test_manager("mallory");
4842 let alice = create_test_manager("alice");
4843
4844 let gid = mallory.create_group("Team").unwrap().group_id;
4845 let mut impostor = Impostor::claiming(&addr("bob"), "mallorys-second-key");
4846 let (_, forged_welcome) =
4847 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4848 impostor.join(&forged_welcome);
4849
4850 let alice_kp = alice.generate_key_package().unwrap();
4851 let (welcome, add_alice_commit) = mallory
4852 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4853 .unwrap();
4854 impostor.merge_commit(&add_alice_commit.ciphertext);
4857
4858 let bytes = welcome.welcome_data.clone();
4861 let staged_welcome = match MlsMessageIn::tls_deserialize_exact(&bytes)
4862 .unwrap()
4863 .extract()
4864 {
4865 MlsMessageBodyIn::Welcome(w) => w,
4866 _ => panic!("not a welcome"),
4867 };
4868 let cfg = MlsGroupJoinConfig::builder()
4869 .use_ratchet_tree_extension(true)
4870 .build();
4871 let group = StagedWelcome::new_from_welcome(&alice.provider, &cfg, staged_welcome, None)
4872 .unwrap()
4873 .into_group(&alice.provider)
4874 .unwrap();
4875 alice.group_manager.save_group(&gid, &group).unwrap();
4876
4877 assert!(
4879 group
4880 .members()
4881 .any(|m| m.credential.serialized_content() == addr("bob").as_bytes()),
4882 "test premise: the forged leaf must be installed"
4883 );
4884
4885 let forged = impostor.encrypt(b"send me the recovery phrase");
4887 let err = alice
4888 .decrypt_from_group(&app_message(&gid, forged, &addr("bob")), &addr("bob"))
4889 .unwrap_err();
4890 assert!(
4891 matches!(err, MlsError::LeafAddressMismatch { ref context, .. }
4892 if *context == crate::error::LeafSource::MessageSender),
4893 "expected a MessageSender binding refusal, got {err:?}"
4894 );
4895
4896 let info = alice.get_group_info(&gid).unwrap().unwrap();
4899 assert!(
4900 !info.members.contains(&addr("bob")),
4901 "roster surfaced an unproven member: {:?}",
4902 info.members
4903 );
4904 assert!(
4905 info.members.contains(&addr("mallory")),
4906 "{:?}",
4907 info.members
4908 );
4909 }
4910
4911 #[test]
4925 fn test_member_renaming_their_own_leaf_to_a_peers_address_is_refused() {
4926 let (alice, bob, gid) = create_test_group_with_bob();
4930 let epoch_before = alice.get_group_info(&gid).unwrap().unwrap().epoch;
4931
4932 let mut group = bob.group_manager.load_group(&gid).unwrap().unwrap();
4933 let signer = bob.get_signer().unwrap();
4934 let forged = CredentialWithKey {
4935 credential: Credential::new(CredentialType::Basic, addr("alice").as_bytes().to_vec()),
4938 signature_key: signer.public().into(),
4939 };
4940 let params = LeafNodeParameters::builder()
4941 .with_credential_with_key(forged)
4942 .build();
4943 let bundle = group
4944 .self_update(&bob.provider, &signer, params)
4945 .expect("MLS permits a credential change; the AS is what refuses it");
4946 group.merge_pending_commit(&bob.provider).unwrap();
4947 bob.group_manager.save_group(&gid, &group).unwrap();
4948 let (commit, _, _) = bundle.into_contents();
4949
4950 let err = alice
4951 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
4952 .unwrap_err();
4953 assert!(
4954 matches!(
4955 err,
4956 MlsError::LeafAddressMismatch { ref claimed, ref context, .. }
4957 if *claimed == addr("alice") && *context == crate::error::LeafSource::CommitPath
4958 ),
4959 "expected a CommitPath binding refusal, got {err:?}"
4960 );
4961
4962 let after = alice.get_group_info(&gid).unwrap().unwrap();
4965 assert_eq!(after.epoch, epoch_before);
4966 assert!(after.members.contains(&addr("bob")), "{:?}", after.members);
4967 assert_eq!(
4968 after
4969 .members
4970 .iter()
4971 .filter(|m| **m == addr("alice"))
4972 .count(),
4973 1,
4974 "alice must appear once, as herself: {:?}",
4975 after.members
4976 );
4977 }
4978
4979 #[test]
4982 fn test_leaf_with_a_nickname_credential_is_refused() {
4983 let mallory = create_test_manager("mallory");
4984 let alice = create_test_manager("alice");
4985 let alice_kp = alice.generate_key_package().unwrap();
4986
4987 let gid = mallory.create_group("Team").unwrap().group_id;
4988 let impostor = Impostor::claiming("bob", "mallorys-second-key");
4989 add_member_bypassing_checks(&mallory, &gid, &impostor.key_package);
4990 let (welcome, _) = mallory
4991 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
4992 .unwrap();
4993
4994 let err = alice.join_group(&welcome).unwrap_err();
4995 assert!(
4996 matches!(err, MlsError::LeafAddressMismatch { ref claimed, .. } if claimed == "bob"),
4997 "a nickname credential was accepted or misreported: {err:?}"
4998 );
4999 }
5000
5001 #[test]
5009 fn test_key_rotation_and_ordinary_traffic_still_pass_the_binding() {
5010 let (alice, bob, gid) = create_test_group_with_bob();
5011
5012 let commit = alice.update_keys(&gid).unwrap();
5013 bob.decrypt_from_group(&commit, &addr("alice"))
5014 .expect("an honest key rotation must still merge");
5015
5016 let ct = alice.encrypt_for_group(&gid, b"after rotation").unwrap();
5017 assert_eq!(
5018 bob.decrypt_from_group(&ct, &addr("alice"))
5019 .unwrap()
5020 .as_deref(),
5021 Some(&b"after rotation"[..])
5022 );
5023
5024 assert!(bob
5026 .get_group_info(&gid)
5027 .unwrap()
5028 .unwrap()
5029 .members
5030 .contains(&addr("bob")));
5031 }
5032
5033 #[test]
5041 fn test_welcome_whose_group_context_has_external_senders_is_refused() {
5042 let mallory = create_test_manager("mallory");
5043 let alice = create_test_manager("alice");
5044
5045 let gid = mallory.create_group("Team").unwrap().group_id;
5046
5047 {
5049 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
5050 let signer = mallory.get_signer().unwrap();
5051 let outsider =
5052 SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
5053 let external = ExternalSender::new(
5054 outsider.public().into(),
5055 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
5056 );
5057 group
5058 .commit_builder()
5059 .propose_group_context_extensions(Extensions::single(Extension::ExternalSenders(
5060 vec![external],
5061 )))
5062 .load_psks(mallory.provider.storage())
5063 .unwrap()
5064 .build(
5065 mallory.provider.rand(),
5066 mallory.provider.crypto(),
5067 &signer,
5068 |_| true,
5069 )
5070 .unwrap()
5071 .stage_commit(&mallory.provider)
5072 .unwrap();
5073 group.merge_pending_commit(&mallory.provider).unwrap();
5074 mallory.group_manager.save_group(&gid, &group).unwrap();
5075 }
5076
5077 let alice_kp = alice.generate_key_package().unwrap();
5078 let (welcome, _) = mallory
5079 .add_group_member(&gid, &addr("alice"), &alice_kp.key_package_data)
5080 .unwrap();
5081
5082 let err = alice.join_group(&welcome).unwrap_err();
5083 assert!(
5084 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
5085 if detail.contains("ExternalSenders")),
5086 "expected the invite to be declined for its ExternalSenders context, got {err:?}"
5087 );
5088 assert!(
5089 alice.get_group_info(&gid).unwrap().is_none(),
5090 "a declined Welcome must not install the group"
5091 );
5092 }
5093
5094 #[test]
5103 fn test_commit_proposing_external_senders_is_refused() {
5104 let (alice, mallory, gid) = create_test_group_with_bob();
5105
5106 let mut group = mallory.group_manager.load_group(&gid).unwrap().unwrap();
5107 let signer = mallory.get_signer().unwrap();
5108
5109 let outsider = SignatureKeyPair::new(DEFAULT_CIPHERSUITE.signature_algorithm()).unwrap();
5111 let external = ExternalSender::new(
5112 outsider.public().into(),
5113 Credential::new(CredentialType::Basic, b"off1outsider".to_vec()),
5114 );
5115 let extensions = Extensions::single(Extension::ExternalSenders(vec![external]));
5116
5117 let bundle = group
5118 .commit_builder()
5119 .propose_group_context_extensions(extensions)
5120 .load_psks(mallory.provider.storage())
5121 .unwrap()
5122 .build(
5123 mallory.provider.rand(),
5124 mallory.provider.crypto(),
5125 &signer,
5126 |_| true,
5127 )
5128 .unwrap()
5129 .stage_commit(&mallory.provider)
5130 .unwrap();
5131 let (commit, _, _) = bundle.into_contents();
5132
5133 let err = alice
5134 .decrypt_from_group(&commit_message(&gid, &commit, &addr("bob")), &addr("bob"))
5135 .unwrap_err();
5136 assert!(
5137 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
5138 if detail.contains("ExternalSenders")),
5139 "expected an ExternalSenders refusal, got {err:?}"
5140 );
5141 }
5142
5143 #[test]
5153 fn test_one_identity_cannot_hold_two_leaves_in_a_group() {
5154 let alice = create_test_manager("alice");
5155 let bob = create_test_manager("bob");
5156
5157 let gid = alice.create_group("Team").unwrap().group_id;
5158 let first = bob.generate_key_package().unwrap();
5159 alice
5160 .add_group_member(&gid, &addr("bob"), &first.key_package_data)
5161 .unwrap();
5162
5163 let second = bob.generate_key_package().unwrap();
5166 assert_ne!(first.package_id, second.package_id);
5167 let err = alice
5168 .add_group_member(&gid, &addr("bob"), &second.key_package_data)
5169 .unwrap_err();
5170 assert!(
5171 matches!(err, MlsError::AddMember(ref m) if m.contains("Duplicate signature key")),
5172 "expected MLS to refuse a duplicate signature key, got {err:?}"
5173 );
5174
5175 alice.remove_group_member(&gid, &addr("bob")).unwrap();
5177 assert!(!alice
5178 .get_group_info(&gid)
5179 .unwrap()
5180 .unwrap()
5181 .members
5182 .contains(&addr("bob")));
5183 }
5184
5185 #[test]
5201 fn test_external_join_proposal_is_refused_as_an_unsupported_sender() {
5202 let (alice, _bob, gid) = create_test_group_with_bob();
5203
5204 let outsider = create_test_manager("mallory");
5208 let kp_bytes = outsider.generate_key_package().unwrap().key_package_data;
5209 let key_package = KeyPackageIn::tls_deserialize_exact(&kp_bytes)
5210 .unwrap()
5211 .validate(alice.provider.crypto(), ProtocolVersion::Mls10)
5212 .unwrap();
5213 let signer = outsider.get_signer().unwrap();
5214
5215 let (mls_group_id, epoch) = {
5216 let group = alice.group_manager.load_group(&gid).unwrap().unwrap();
5217 (group.group_id().clone(), group.epoch())
5218 };
5219 let proposal =
5220 JoinProposal::new::<MlsStorageAdapter>(key_package, mls_group_id, epoch, &signer)
5221 .expect(
5222 "an external join proposal is well-formed; the SDK just declines to honour it",
5223 );
5224
5225 let err = alice
5226 .decrypt_from_group(
5227 &app_message(
5228 &gid,
5229 proposal.tls_serialize_detached().unwrap(),
5230 &addr("mallory"),
5231 ),
5232 &addr("mallory"),
5233 )
5234 .unwrap_err();
5235 assert!(
5236 matches!(err, MlsError::UnsupportedSender { ref detail, .. }
5237 if detail.contains("not a group member")),
5238 "expected an UnsupportedSender refusal, got {err:?}"
5239 );
5240 }
5241
5242 #[test]
5256 fn test_roster_read_counts_the_leaves_it_skips() {
5257 let (alice, _bob, gid) = create_test_group_with_bob();
5258
5259 let before = alice.get_group_info(&gid).unwrap().unwrap();
5260 assert_eq!(
5261 before.unproven_members, 0,
5262 "an honest group must report no unproven members: {:?}",
5263 before.members
5264 );
5265 let honest_members = before.members.len();
5266
5267 let impostor = Impostor::claiming(&addr("carol"), "an-attackers-key");
5270 add_member_bypassing_checks(&alice, &gid, &impostor.key_package);
5271
5272 let after = alice.get_group_info(&gid).unwrap().unwrap();
5273 assert_eq!(
5274 after.unproven_members, 1,
5275 "the skipped leaf must be counted, not only dropped"
5276 );
5277 assert_eq!(
5278 after.members.len(),
5279 honest_members,
5280 "the roster must be unchanged: {:?}",
5281 after.members
5282 );
5283 assert!(
5284 !after.members.contains(&addr("carol")),
5285 "roster surfaced the unproven member: {:?}",
5286 after.members
5287 );
5288 }
5289
5290 fn key_package_with_window(label: &str, lifetime: Lifetime) -> (String, Vec<u8>) {
5300 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
5301 let provider = MlsProvider::new(MlsStorageAdapter::new(storage));
5302 let (keys, address) = test_identity(label);
5303 keys.store(provider.storage()).unwrap();
5304
5305 let credential = CredentialWithKey {
5306 credential: Credential::new(
5307 CredentialType::Basic,
5308 address.to_string().as_bytes().to_vec(),
5309 ),
5310 signature_key: keys.public().into(),
5311 };
5312 let bytes = KeyPackage::builder()
5313 .key_package_lifetime(lifetime)
5314 .build(DEFAULT_CIPHERSUITE, &provider, &keys, credential)
5315 .unwrap()
5316 .key_package()
5317 .tls_serialize_detached()
5318 .unwrap();
5319
5320 (address.to_string(), bytes)
5321 }
5322
5323 fn now_secs() -> u64 {
5324 offline_protocol_core::time::SystemTime::now()
5325 .duration_since(offline_protocol_core::time::UNIX_EPOCH)
5326 .unwrap()
5327 .as_secs()
5328 }
5329
5330 #[test]
5336 fn a_window_that_has_not_started_by_our_clock_is_reported_as_such() {
5337 let (manager, _) = create_addressed_manager("alice");
5338 let year = 365 * 24 * 60 * 60;
5339 let (peer, bytes) = key_package_with_window(
5340 "bob",
5341 Lifetime::init(
5342 now_secs() + year - 3600,
5343 now_secs() + year + 30 * 24 * 60 * 60,
5344 ),
5345 );
5346
5347 let err = manager
5348 .import_key_package(&peer, &bytes)
5349 .expect_err("a window that starts in a year is not valid now");
5350 assert!(
5351 matches!(
5352 err,
5353 MlsError::KeyPackageOutsideValidityWindow { expired: false }
5354 ),
5355 "refused for the wrong reason: {err:?}"
5356 );
5357 }
5358
5359 #[test]
5362 fn a_window_that_closed_by_our_clock_is_reported_as_such() {
5363 let (manager, _) = create_addressed_manager("alice");
5364 let day = 24 * 60 * 60;
5365 let (peer, bytes) = key_package_with_window(
5366 "bob",
5367 Lifetime::init(now_secs() - 60 * day, now_secs() - 30 * day),
5368 );
5369
5370 let err = manager
5371 .import_key_package(&peer, &bytes)
5372 .expect_err("a window that closed a month ago is not valid now");
5373 assert!(
5374 matches!(
5375 err,
5376 MlsError::KeyPackageOutsideValidityWindow { expired: true }
5377 ),
5378 "refused for the wrong reason: {err:?}"
5379 );
5380 }
5381
5382 #[test]
5386 fn only_the_window_refusal_changes_type() {
5387 let (manager, _) = create_addressed_manager("alice");
5388 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5389 let err = manager.import_key_package(&peer, &bytes).unwrap_err();
5390 assert!(matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")));
5391
5392 let err = manager
5393 .import_key_package(&peer, b"not a key package")
5394 .unwrap_err();
5395 assert!(matches!(err, MlsError::InvalidKeyPackage(_)), "{err:?}");
5396 }
5397
5398 #[test]
5401 fn a_year_long_validity_window_is_refused_at_import() {
5402 let (manager, _) = create_addressed_manager("alice");
5403
5404 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5405
5406 let err = manager
5407 .import_key_package(&peer, &bytes)
5408 .expect_err("a year-long window must be refused");
5409 assert!(
5410 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5411 "refused for the wrong reason: {err:?}"
5412 );
5413 }
5414
5415 #[test]
5421 fn a_wide_window_written_straight_into_the_store_is_refused_on_read() {
5422 let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
5423 let (keys, address) = test_identity("alice");
5424 seed_identity(&storage, &keys);
5425 let manager = MlsManager::new(address.to_string(), Arc::clone(&storage)).unwrap();
5426
5427 let (peer, bytes) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5428 storage
5429 .store(StorageKeyType::ContactKeyPackage.as_str(), &peer, &bytes)
5430 .unwrap();
5431
5432 let err = manager
5433 .get_contact_key_package(&peer)
5434 .expect_err("a cached year-long window must be refused too");
5435 assert!(
5436 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5437 "refused for the wrong reason: {err:?}"
5438 );
5439 }
5440
5441 #[test]
5448 fn our_own_key_package_states_the_lifetime_we_document() {
5449 let (manager, _) = create_addressed_manager("alice");
5450
5451 let bundle = manager.generate_key_package().unwrap();
5452 let key_package = KeyPackageIn::tls_deserialize_exact(bundle.key_package_data.as_slice())
5453 .unwrap()
5454 .validate(manager.provider.crypto(), ProtocolVersion::Mls10)
5455 .unwrap();
5456
5457 let window = key_package.life_time();
5458 assert_eq!(
5459 window.not_after() - window.not_before(),
5460 DEFAULT_KEY_PACKAGE_LIFETIME_SECS + LIFETIME_SKEW_MARGIN_SECS,
5461 "the window on the wire is not the lifetime this crate declares"
5462 );
5463 assert!(
5464 MlsManager::verify_lifetime_bound(&key_package).is_ok(),
5465 "this install mints a package it would refuse from a peer"
5466 );
5467 }
5468
5469 #[test]
5477 fn a_year_long_validity_window_is_refused_by_a_group_invite() {
5478 let alice = create_test_manager("alice");
5479 let group_id = alice.create_group("Test Group").unwrap().group_id;
5480
5481 let (bob, wide) = key_package_with_window("bob", Lifetime::new(365 * 24 * 60 * 60));
5482 let err = alice
5483 .add_group_member(&group_id, &bob, &wide)
5484 .expect_err("a year-long window must be refused by the group path too");
5485 assert!(
5486 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5487 "refused for the wrong reason: {err:?}"
5488 );
5489
5490 let bob_manager = create_test_manager("bob");
5493 let bob_kp = bob_manager.generate_key_package().unwrap();
5494 alice
5495 .add_group_member(&group_id, &bob, &bob_kp.key_package_data)
5496 .expect("a package inside the cap is admitted");
5497 }
5498
5499 #[test]
5502 fn a_window_exactly_at_the_cap_is_admitted_and_one_second_over_is_not() {
5503 let cap = MAX_ACCEPTED_KEY_PACKAGE_LIFETIME.as_secs();
5504 let (manager, _) = create_addressed_manager("alice");
5505
5506 let start = now_secs() - 10;
5509
5510 let (at_cap, bytes) = key_package_with_window("bob", Lifetime::init(start, start + cap));
5511 manager
5512 .import_key_package(&at_cap, &bytes)
5513 .expect("a window exactly at the cap is admitted");
5514
5515 let (over_cap, bytes) =
5516 key_package_with_window("carol", Lifetime::init(start, start + cap + 1));
5517 let err = manager
5518 .import_key_package(&over_cap, &bytes)
5519 .expect_err("one second over the cap must be refused");
5520 assert!(
5521 matches!(&err, MlsError::InvalidKeyPackage(m) if m.contains("wider than")),
5522 "refused for the wrong reason: {err:?}"
5523 );
5524 }
5525}