core_crypto/mls/conversation/
mod.rs1mod commit;
15mod config;
16mod error;
17mod group_info;
18mod id;
19mod immutable;
20mod mutable;
21mod orphan_welcome;
22mod pending;
23mod welcome;
24
25pub(crate) use pending::PendingConversation;
26
27pub use self::{
28 commit::CommitBundle,
29 config::{ConversationConfiguration, CustomConfiguration, WirePolicy},
30 error::{Error, Result},
31 group_info::{GroupInfoBundle, GroupInfoEncryptionType, GroupInfoPayload, RatchetTreeType},
32 id::{ConversationId, ConversationIdRef},
33 immutable::Conversation,
34 mutable::{
35 ConversationMut,
36 decrypt::{BufferedDecryptedMessage, DecryptedMessage},
37 },
38 welcome::WelcomeMessage,
39};
40use crate::bytes_wrapper;
41
42bytes_wrapper!(
43 #[derive(Clone)]
47 SecretKey
48);
49
50bytes_wrapper!(
51 #[derive(Clone)]
55 ExternalSenderKey
56);
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::test_utils::*;
62
63 #[apply(all_cred_cipher)]
64 pub async fn create_self_conversation_should_succeed(case: TestContext) {
65 let [alice] = case.sessions().await;
66 Box::pin(async move {
67 let conversation = case.create_conversation([&alice]).await;
68 assert_eq!(1, conversation.member_count().await);
69 let alice_can_send_message = conversation.guard().await.encrypt_message(b"me").await;
70 assert!(alice_can_send_message.is_ok());
71 })
72 .await;
73 }
74
75 #[apply(all_cred_cipher)]
76 pub async fn create_1_1_conversation_should_succeed(case: TestContext) {
77 let [alice, bob] = case.sessions().await;
78 Box::pin(async move {
79 let conversation = case.create_conversation([&alice, &bob]).await;
80 assert_eq!(2, conversation.member_count().await);
81 assert!(conversation.is_functional_and_contains([&alice, &bob]).await);
82 })
83 .await;
84 }
85
86 #[apply(all_cred_cipher)]
87 pub async fn create_many_people_conversation(case: TestContext) {
88 const SIZE_PLUS_1: usize = GROUP_SAMPLE_SIZE + 1;
89 let alice_and_friends = case.sessions::<SIZE_PLUS_1>().await;
90 Box::pin(async move {
91 let alice = &alice_and_friends[0];
92 let conversation = case.create_conversation([alice]).await;
93
94 let bob_and_friends = &alice_and_friends[1..];
95 let conversation = conversation.invite_notify(bob_and_friends).await;
96
97 assert_eq!(conversation.member_count().await, 1 + GROUP_SAMPLE_SIZE);
98 assert!(conversation.is_functional_and_contains(&alice_and_friends).await);
99 })
100 .await;
101 }
102
103 mod wire_identity_getters {
104 use super::Error;
105 use crate::{
106 ClientId, CredentialType, DeviceStatus, E2eiConversationState, mls::conversation::Conversation,
107 test_utils::*,
108 };
109
110 async fn all_identities_check<const N: usize>(
111 conversation: &Conversation,
112 user_ids: &[String; N],
113 expected_sizes: [usize; N],
114 ) {
115 let all_identities = conversation.get_user_identities(user_ids).await.unwrap();
116 assert_eq!(all_identities.len(), N);
117 for (expected_size, user_id) in expected_sizes.into_iter().zip(user_ids.iter()) {
118 let alice_identities = all_identities.get(user_id).unwrap();
119 assert_eq!(alice_identities.len(), expected_size);
120 }
121 let not_found = conversation
123 .get_user_identities(&["aaaaaaaaaaaaa".to_string()])
124 .await
125 .unwrap();
126 assert!(not_found.is_empty());
127
128 let invalid = conversation.get_user_identities(&[]).await;
130 assert!(matches!(invalid.unwrap_err(), Error::CallerError(_)));
131 }
132
133 async fn check_identities_device_status<const N: usize>(
134 conversation: &Conversation,
135 client_ids: &[ClientId; N],
136 name_status: &[(impl ToString, DeviceStatus); N],
137 ) {
138 let mut identities = conversation.get_device_identities(client_ids).await.unwrap();
139
140 for (user_name, status) in name_status.iter() {
141 let client_identity = identities.remove(
142 identities
143 .iter()
144 .position(|i| i.x509_identity.as_ref().unwrap().display_name == user_name.to_string())
145 .unwrap(),
146 );
147 assert_eq!(client_identity.status, *status);
148 }
149 assert!(identities.is_empty());
150
151 assert_eq!(
152 conversation.e2ei_conversation_state().await.unwrap(),
153 E2eiConversationState::NotVerified
154 );
155 }
156
157 #[ignore]
159 #[macro_rules_attribute::apply(smol_macros::test)]
160 async fn should_read_device_identities() {
161 let case = TestContext::default_x509();
162
163 let [alice_android, alice_ios] = case.sessions().await;
164 Box::pin(async move {
165 let conversation = case.create_conversation([&alice_android, &alice_ios]).await;
166
167 let (android_id, ios_id) = (alice_android.get_client_id().await, alice_ios.get_client_id().await);
168
169 let mut android_ids = conversation
170 .guard()
171 .await
172 .get_device_identities(&[android_id.clone(), ios_id.clone()])
173 .await
174 .unwrap();
175 android_ids.sort_by(|a, b| a.client_id.cmp(&b.client_id));
176 assert_eq!(android_ids.len(), 2);
177 let mut ios_ids = conversation
178 .guard_of(&alice_ios)
179 .await
180 .get_device_identities(&[android_id.clone(), ios_id.clone()])
181 .await
182 .unwrap();
183 ios_ids.sort_by(|a, b| a.client_id.cmp(&b.client_id));
184 assert_eq!(ios_ids.len(), 2);
185
186 assert_eq!(android_ids, ios_ids);
187
188 let android_identities = conversation
189 .guard()
190 .await
191 .get_device_identities(&[android_id])
192 .await
193 .unwrap();
194 let android_id = android_identities.first().unwrap();
195 assert_eq!(
196 android_id.client_id.as_bytes(),
197 alice_android.transaction.client_id().await.unwrap().0.as_slice()
198 );
199
200 let ios_identities = conversation
201 .guard()
202 .await
203 .get_device_identities(&[ios_id])
204 .await
205 .unwrap();
206 let ios_id = ios_identities.first().unwrap();
207 assert_eq!(
208 ios_id.client_id.as_bytes(),
209 alice_ios.transaction.client_id().await.unwrap().0.as_slice()
210 );
211
212 let empty_slice: &[ClientId] = &[];
213 let invalid = conversation.guard().await.get_device_identities(empty_slice).await;
214 assert!(matches!(invalid.unwrap_err(), Error::CallerError(_)));
215 })
216 .await
217 }
218
219 #[ignore]
221 #[macro_rules_attribute::apply(smol_macros::test)]
222 async fn should_read_revoked_device() {
223 let case = TestContext::default_x509();
224 let rupert_user_id = uuid::Uuid::new_v4();
225 let bob_user_id = uuid::Uuid::new_v4();
226 let alice_user_id = uuid::Uuid::new_v4();
227
228 let [rupert_client_id] = case.x509_client_ids_for_user(&rupert_user_id);
229 let [alice_client_id] = case.x509_client_ids_for_user(&alice_user_id);
230 let [bob_client_id] = case.x509_client_ids_for_user(&bob_user_id);
231
232 let sessions = case
233 .sessions_x509_with_client_ids_and_revocation(
234 [alice_client_id.clone(), bob_client_id.clone(), rupert_client_id.clone()],
235 &[rupert_user_id.to_string()],
236 )
237 .await;
238
239 Box::pin(async move {
240 let [alice, bob, rupert] = &sessions;
241 let conversation = case.create_conversation(&sessions).await;
242
243 let (alice_id, bob_id, rupert_id) = (
244 alice.get_client_id().await,
245 bob.get_client_id().await,
246 rupert.get_client_id().await,
247 );
248
249 let client_ids = [alice_id, bob_id, rupert_id];
250 let name_status = [
251 (alice_user_id, DeviceStatus::Valid),
252 (bob_user_id, DeviceStatus::Valid),
253 (rupert_user_id, DeviceStatus::Revoked),
254 ];
255
256 for _ in 0..2 {
258 for session in sessions.iter() {
259 let conversation = conversation.guard_of(session).await;
260 check_identities_device_status(&conversation, &client_ids, &name_status).await;
261 }
262 }
263 })
264 .await
265 }
266
267 #[macro_rules_attribute::apply(smol_macros::test)]
268 async fn should_not_fail_when_basic() {
269 let case = TestContext::default();
270
271 let [alice_android, alice_ios] = case.sessions().await;
272 Box::pin(async move {
273 let conversation = case.create_conversation([&alice_android, &alice_ios]).await;
274
275 let (android_id, ios_id) = (alice_android.get_client_id().await, alice_ios.get_client_id().await);
276
277 let mut android_ids = conversation
278 .guard()
279 .await
280 .get_device_identities(&[android_id.clone(), ios_id.clone()])
281 .await
282 .unwrap();
283 android_ids.sort();
284
285 let mut ios_ids = conversation
286 .guard_of(&alice_ios)
287 .await
288 .get_device_identities(&[android_id, ios_id])
289 .await
290 .unwrap();
291 ios_ids.sort();
292
293 assert_eq!(ios_ids.len(), 2);
294 assert_eq!(ios_ids, android_ids);
295
296 assert!(ios_ids.iter().all(|i| {
297 matches!(i.credential_type, CredentialType::Basic)
298 && matches!(i.status, DeviceStatus::Valid)
299 && i.x509_identity.is_none()
300 && !i.thumbprint.is_empty()
301 && !i.client_id.is_empty()
302 }));
303 })
304 .await
305 }
306
307 #[ignore]
309 #[macro_rules_attribute::apply(smol_macros::test)]
310 async fn should_read_users() {
311 let case = TestContext::default_x509();
312 let [alice_android, alice_ios] = case.x509_client_ids_for_user(&uuid::Uuid::new_v4());
313 let [bob_android] = case.x509_client_ids();
314
315 let sessions = case
316 .sessions_x509_with_client_ids([alice_android, alice_ios, bob_android])
317 .await;
318
319 Box::pin(async move {
320 let conversation = case.create_conversation(&sessions).await;
321
322 let nb_members = conversation.member_count().await;
323 assert_eq!(nb_members, 3);
324
325 let [alice_android, alice_ios, bob_android] = &sessions;
326 assert_eq!(alice_android.get_user_id().await, alice_ios.get_user_id().await);
327
328 let alice_user_id = alice_android.get_user_id().await;
330 let alice_identities = conversation
331 .guard()
332 .await
333 .get_user_identities(std::slice::from_ref(&alice_user_id))
334 .await
335 .unwrap();
336 assert_eq!(alice_identities.len(), 1);
337 let identities = alice_identities.get(&alice_user_id).unwrap();
338 assert_eq!(identities.len(), 2);
339
340 let bob_user_id = bob_android.get_user_id().await;
342 let bob_identities = conversation
343 .guard()
344 .await
345 .get_user_identities(std::slice::from_ref(&bob_user_id))
346 .await
347 .unwrap();
348 assert_eq!(bob_identities.len(), 1);
349 let identities = bob_identities.get(&bob_user_id).unwrap();
350 assert_eq!(identities.len(), 1);
351
352 let user_ids = [alice_user_id, bob_user_id];
353 let expected_sizes = [2, 1];
354
355 for session in &sessions {
356 all_identities_check(&*conversation.guard_of(session).await, &user_ids, expected_sizes).await;
357 }
358 })
359 .await
360 }
361 }
362
363 mod export_secret {
364 use openmls::prelude::ExportSecretError;
365
366 use super::*;
367 use crate::OpenMlsErrorKind;
368
369 #[apply(all_cred_cipher)]
370 pub async fn can_export_secret_key(case: TestContext) {
371 let [alice] = case.sessions().await;
372 Box::pin(async move {
373 let conversation = case.create_conversation([&alice]).await;
374
375 let key_length = 128;
376 let result = conversation.guard().await.export_secret_key(key_length).await;
377 assert!(result.is_ok());
378 assert_eq!(result.unwrap().len(), key_length);
379 })
380 .await
381 }
382
383 #[apply(all_cred_cipher)]
384 pub async fn cannot_export_secret_key_invalid_length(case: TestContext) {
385 let [alice] = case.sessions().await;
386 Box::pin(async move {
387 let conversation = case.create_conversation([&alice]).await;
388
389 let result = conversation.guard().await.export_secret_key(usize::MAX).await;
390 let error = result.unwrap_err();
391 assert!(innermost_source_matches!(
392 error,
393 OpenMlsErrorKind::MlsExportSecretError(ExportSecretError::KeyLengthTooLong)
394 ));
395 })
396 .await
397 }
398 }
399
400 mod get_client_ids {
401 use super::*;
402
403 #[apply(all_cred_cipher)]
404 pub async fn can_get_client_ids(case: TestContext) {
405 let [alice, bob] = case.sessions().await;
406 Box::pin(async move {
407 let conversation = case.create_conversation([&alice]).await;
408
409 assert_eq!(conversation.guard().await.get_client_ids().await.len(), 1);
410
411 let conversation = conversation.invite_notify([&bob]).await;
412
413 assert_eq!(conversation.guard().await.get_client_ids().await.len(), 2);
414 })
415 .await
416 }
417 }
418
419 mod external_sender {
420 use super::*;
421
422 #[apply(all_cred_cipher)]
423 pub async fn should_fetch_ext_sender(mut case: TestContext) {
424 let [alice, external_sender] = case.sessions().await;
425 Box::pin(async move {
426 use core_crypto_keystore::Sha256Hash;
427
428 let conversation = case
429 .create_conversation_with_external_sender(&external_sender, [&alice])
430 .await;
431
432 let alice_ext_sender = conversation.guard().await.get_external_sender().await.unwrap();
433 assert!(!alice_ext_sender.is_empty());
434 assert_eq!(
435 Sha256Hash::hash_from(alice_ext_sender),
436 external_sender.initial_credential.public_key_hash()
437 );
438 })
439 .await
440 }
441 }
442}