1use super::{error::Error, error::Result};
2#[cfg(not(target_family = "wasm"))]
3use crate::e2e_identity::refresh_token::RefreshToken;
4use crate::{
5 KeystoreError, MlsError, RecursiveError,
6 e2e_identity::NewCrlDistributionPoints,
7 mls::credential::{ext::CredentialExt, x509::CertificatePrivateKey},
8 prelude::{CertificateBundle, E2eiEnrollment, MlsCiphersuite, MlsCredentialType},
9 transaction_context::TransactionContext,
10};
11use core_crypto_keystore::{CryptoKeystoreMls, connection::FetchFromDatabase, entities::MlsKeyPackage};
12use openmls::prelude::KeyPackage;
13use openmls_traits::OpenMlsCryptoProvider;
14
15impl TransactionContext {
16 pub async fn e2ei_new_activation_enrollment(
22 &self,
23 display_name: String,
24 handle: String,
25 team: Option<String>,
26 expiry_sec: u32,
27 ciphersuite: MlsCiphersuite,
28 ) -> Result<E2eiEnrollment> {
29 let mls_provider = self
30 .mls_provider()
31 .await
32 .map_err(RecursiveError::transaction("getting mls provider"))?;
33 let cb = self
35 .session()
36 .await
37 .map_err(RecursiveError::transaction("getting mls client"))?
38 .find_most_recent_credential_bundle(ciphersuite.signature_algorithm(), MlsCredentialType::Basic)
39 .await
40 .map_err(|_| Error::MissingExistingClient(MlsCredentialType::Basic))?;
41 let client_id = cb.credential().identity().into();
42
43 let sign_keypair = Some(
44 cb.signature_key()
45 .try_into()
46 .map_err(RecursiveError::e2e_identity("creating E2eiSignatureKeypair"))?,
47 );
48
49 E2eiEnrollment::try_new(
50 client_id,
51 display_name,
52 handle,
53 team,
54 expiry_sec,
55 &mls_provider,
56 ciphersuite,
57 sign_keypair,
58 #[cfg(not(target_family = "wasm"))]
59 None, )
61 .map_err(RecursiveError::e2e_identity("creating new enrollment"))
62 .map_err(Into::into)
63 }
64
65 pub async fn e2ei_new_rotate_enrollment(
71 &self,
72 display_name: Option<String>,
73 handle: Option<String>,
74 team: Option<String>,
75 expiry_sec: u32,
76 ciphersuite: MlsCiphersuite,
77 ) -> Result<E2eiEnrollment> {
78 let mls_provider = self
79 .mls_provider()
80 .await
81 .map_err(RecursiveError::transaction("getting mls provider"))?;
82 let cb = self
84 .session()
85 .await
86 .map_err(RecursiveError::transaction("getting mls client"))?
87 .find_most_recent_credential_bundle(ciphersuite.signature_algorithm(), MlsCredentialType::X509)
88 .await
89 .map_err(|_| Error::MissingExistingClient(MlsCredentialType::X509))?;
90 let client_id = cb.credential().identity().into();
91 let sign_keypair = Some(
92 cb.signature_key()
93 .try_into()
94 .map_err(RecursiveError::e2e_identity("creating E2eiSignatureKeypair"))?,
95 );
96 let existing_identity = cb
97 .to_mls_credential_with_key()
98 .extract_identity(ciphersuite, None)
99 .map_err(RecursiveError::mls_credential("extracting identity"))?
100 .x509_identity
101 .ok_or(Error::ImplementationError)?;
102
103 let display_name = display_name.unwrap_or(existing_identity.display_name);
104 let handle = handle.unwrap_or(existing_identity.handle);
105
106 E2eiEnrollment::try_new(
107 client_id,
108 display_name,
109 handle,
110 team,
111 expiry_sec,
112 &mls_provider,
113 ciphersuite,
114 sign_keypair,
115 #[cfg(not(target_family = "wasm"))]
116 Some(
117 RefreshToken::find(&mls_provider.keystore())
118 .await
119 .map_err(RecursiveError::e2e_identity("finding refreh token"))?,
120 ), )
122 .map_err(RecursiveError::e2e_identity("creating new enrollment"))
123 .map_err(Into::into)
124 }
125
126 pub async fn save_x509_credential(
138 &self,
139 enrollment: &mut E2eiEnrollment,
140 certificate_chain: String,
141 ) -> Result<NewCrlDistributionPoints> {
142 let sk = enrollment
143 .get_sign_key_for_mls()
144 .map_err(RecursiveError::e2e_identity("getting sign key for mls"))?;
145 let cs = *enrollment.ciphersuite();
146 let certificate_chain = enrollment
147 .certificate_response(
148 certificate_chain,
149 self.mls_provider()
150 .await
151 .map_err(RecursiveError::transaction("getting provider"))?
152 .authentication_service()
153 .borrow()
154 .await
155 .as_ref()
156 .ok_or(Error::PkiEnvironmentUnset)?,
157 )
158 .await
159 .map_err(RecursiveError::e2e_identity("getting certificate response"))?;
160
161 let private_key = CertificatePrivateKey {
162 value: sk,
163 signature_scheme: cs.signature_algorithm(),
164 };
165
166 let crl_new_distribution_points = self
167 .extract_dp_on_init(&certificate_chain[..])
168 .await
169 .map_err(RecursiveError::mls_credential("extracting dp on init"))?;
170
171 let cert_bundle = CertificateBundle {
172 certificate_chain,
173 private_key,
174 };
175 let client = &self
176 .session()
177 .await
178 .map_err(RecursiveError::transaction("getting mls provider"))?;
179
180 client
181 .save_new_x509_credential_bundle(
182 &self
183 .mls_provider()
184 .await
185 .map_err(RecursiveError::transaction("getting mls provider"))?
186 .keystore(),
187 cs.signature_algorithm(),
188 cert_bundle,
189 )
190 .await
191 .map_err(RecursiveError::mls_client("saving new x509 credential bundle"))?;
192
193 Ok(crl_new_distribution_points)
194 }
195
196 pub async fn delete_stale_key_packages(&self, cipher_suite: MlsCiphersuite) -> Result<()> {
199 let signature_scheme = cipher_suite.signature_algorithm();
200 let keystore = self
201 .keystore()
202 .await
203 .map_err(RecursiveError::transaction("getting keystore"))?;
204 let nb_kp = keystore
205 .count::<MlsKeyPackage>()
206 .await
207 .map_err(KeystoreError::wrap("counting key packages"))?;
208 let kps: Vec<KeyPackage> = keystore
209 .mls_fetch_keypackages(nb_kp as u32)
210 .await
211 .map_err(KeystoreError::wrap("fetching key packages"))?;
212 let client = self
213 .session()
214 .await
215 .map_err(RecursiveError::transaction("getting mls client"))?;
216
217 let cb = client
218 .find_most_recent_credential_bundle(signature_scheme, MlsCredentialType::X509)
219 .await
220 .map_err(RecursiveError::mls_client("finding most recent credential bundle"))?;
221
222 let mut kp_refs = vec![];
223
224 let provider = self
225 .mls_provider()
226 .await
227 .map_err(RecursiveError::transaction("getting mls provider"))?;
228 for kp in kps {
229 let kp_cred = kp.leaf_node().credential().mls_credential();
230 let local_cred = cb.credential().mls_credential();
231 if kp_cred != local_cred {
232 let kpr = kp
233 .hash_ref(provider.crypto())
234 .map_err(MlsError::wrap("computing keypackage hashref"))?;
235 kp_refs.push(kpr);
236 };
237 }
238 self.delete_keypackages(&kp_refs)
239 .await
240 .map_err(RecursiveError::mls_client("deleting keypackages"))?;
241 Ok(())
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::{
249 e2e_identity::enrollment::test_utils as e2ei_utils, mls::credential::ext::CredentialExt,
250 prelude::key_package::INITIAL_KEYING_MATERIAL_COUNT, test_utils::*,
251 };
252 use core_crypto_keystore::entities::{EntityFindParams, MlsCredential};
253 use openmls::prelude::SignaturePublicKey;
254 use std::collections::HashSet;
255 use tls_codec::Deserialize;
256 use wasm_bindgen_test::*;
257
258 wasm_bindgen_test_configure!(run_in_browser);
259
260 pub(crate) mod all {
261 use e2ei_utils::E2EI_EXPIRY;
262
263 use super::*;
264 use crate::test_utils::context::TEAM;
265
266 #[apply(all_cred_cipher)]
267 #[wasm_bindgen_test]
268 async fn enrollment_should_rotate_all(case: TestCase) {
269 run_test_with_client_ids(
270 case.clone(),
271 ["alice", "bob", "charlie"],
272 move |[mut alice_central, mut bob_central, mut charlie_central]| {
273 Box::pin(async move {
274 const N: usize = 50;
275 const NB_KEY_PACKAGE: usize = 50;
276
277 let mut ids = vec![];
278
279 let x509_test_chain_arc = e2ei_utils::failsafe_ctx(
280 &mut [&mut alice_central, &mut bob_central, &mut charlie_central],
281 case.signature_scheme(),
282 )
283 .await;
284
285 let x509_test_chain = x509_test_chain_arc.as_ref().as_ref().unwrap();
286
287 for _ in 0..N {
288 let id = conversation_id();
289 alice_central
290 .context
291 .new_conversation(&id, case.credential_type, case.cfg.clone())
292 .await
293 .unwrap();
294 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
295 ids.push(id)
296 }
297
298 let before_rotate = alice_central.context.count_entities().await;
300 assert_eq!(before_rotate.key_package, INITIAL_KEYING_MATERIAL_COUNT);
301
302 assert_eq!(before_rotate.hpke_private_key, INITIAL_KEYING_MATERIAL_COUNT);
303
304 assert_eq!(before_rotate.encryption_keypair, INITIAL_KEYING_MATERIAL_COUNT);
306
307 assert_eq!(before_rotate.credential, 1);
308 let old_credential = alice_central
309 .find_most_recent_credential_bundle(case.signature_scheme(), case.credential_type)
310 .await
311 .unwrap()
312 .clone();
313
314 let is_renewal = case.credential_type == MlsCredentialType::X509;
315
316 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
317 &mut alice_central,
318 &case,
319 x509_test_chain,
320 None,
321 is_renewal,
322 e2ei_utils::init_activation_or_rotation,
323 e2ei_utils::noop_restore,
324 )
325 .await
326 .unwrap();
327
328 alice_central
329 .context
330 .save_x509_credential(&mut enrollment, cert)
331 .await
332 .unwrap();
333
334 let cb = alice_central
335 .find_most_recent_credential_bundle(case.signature_scheme(), MlsCredentialType::X509)
336 .await
337 .unwrap();
338
339 let result = alice_central
340 .create_key_packages_and_update_credential_in_all_conversations(
341 &cb,
342 *enrollment.ciphersuite(),
343 NB_KEY_PACKAGE,
344 )
345 .await
346 .unwrap();
347
348 let after_rotate = alice_central.context.count_entities().await;
349 assert_eq!(after_rotate.key_package - before_rotate.key_package, NB_KEY_PACKAGE);
351
352 assert_eq!(after_rotate.credential - before_rotate.credential, 1);
354
355 for (id, commit) in result.conversation_ids_and_commits.into_iter() {
356 let decrypted = bob_central
357 .context
358 .conversation(&id)
359 .await
360 .unwrap()
361 .decrypt_message(commit.commit.to_bytes().unwrap())
362 .await
363 .unwrap();
364 alice_central.verify_sender_identity(&case, &decrypted).await;
365
366 alice_central
367 .verify_local_credential_rotated(
368 &id,
369 e2ei_utils::NEW_HANDLE,
370 e2ei_utils::NEW_DISPLAY_NAME,
371 )
372 .await;
373 }
374
375 let new_credentials = result
377 .new_key_packages
378 .iter()
379 .map(|kp| kp.leaf_node().to_credential_with_key());
380 for c in new_credentials {
381 assert_eq!(c.credential.credential_type(), openmls::prelude::CredentialType::X509);
382 let identity = c.extract_identity(case.ciphersuite(), None).unwrap();
383 assert_eq!(
384 identity.x509_identity.as_ref().unwrap().display_name,
385 e2ei_utils::NEW_DISPLAY_NAME
386 );
387 assert_eq!(
388 identity.x509_identity.as_ref().unwrap().handle,
389 format!("wireapp://%40{}@world.com", e2ei_utils::NEW_HANDLE)
390 );
391 }
392
393 assert!(
397 alice_central
398 .find_credential_bundle(
399 case.signature_scheme(),
400 case.credential_type,
401 &old_credential.signature_key.public().into()
402 )
403 .await
404 .is_some()
405 );
406
407 let before_delete = alice_central.context.count_entities().await;
409 assert_eq!(
410 before_delete.hpke_private_key - before_rotate.hpke_private_key,
411 NB_KEY_PACKAGE
412 );
413
414 assert_eq!(before_delete.key_package - before_rotate.key_package, NB_KEY_PACKAGE);
416
417 assert!(
419 alice_central
420 .find_signature_keypair_from_keystore(old_credential.signature_key.public())
421 .await
422 .is_some()
423 );
424
425 alice_central
428 .context
429 .delete_stale_key_packages(case.ciphersuite())
430 .await
431 .unwrap();
432
433 let nb_x509_kp = alice_central
435 .count_key_package(case.ciphersuite(), Some(MlsCredentialType::X509))
436 .await;
437 assert_eq!(nb_x509_kp, NB_KEY_PACKAGE);
438 let nb_basic_kp = alice_central
440 .count_key_package(case.ciphersuite(), Some(MlsCredentialType::Basic))
441 .await;
442 assert_eq!(nb_basic_kp, 0);
443
444 let after_delete = alice_central.context.count_entities().await;
448 assert_eq!(after_delete.credential, 1);
449 assert!(
450 alice_central
451 .find_credential_from_keystore(&old_credential)
452 .await
453 .is_none()
454 );
455
456 assert_eq!(after_delete.hpke_private_key, NB_KEY_PACKAGE);
458
459 assert_eq!(
461 after_rotate.encryption_keypair - after_delete.encryption_keypair,
462 INITIAL_KEYING_MATERIAL_COUNT
463 );
464
465 let id = conversation_id();
467 charlie_central
468 .context
469 .new_conversation(&id, case.credential_type, case.cfg.clone())
470 .await
471 .unwrap();
472 let alice = alice_central
474 .rand_key_package_of_type(&case, MlsCredentialType::X509)
475 .await;
476 charlie_central
477 .invite_all_members(&case, &id, [(&alice_central, alice)])
478 .await
479 .unwrap();
480 })
481 },
482 )
483 .await
484 }
485
486 #[apply(all_cred_cipher)]
487 #[wasm_bindgen_test]
488 async fn should_restore_credentials_in_order(case: TestCase) {
489 run_test_with_client_ids(case.clone(), ["alice"], move |[mut alice_central]| {
490 Box::pin(async move {
491 let x509_test_chain_arc =
492 e2ei_utils::failsafe_ctx(&mut [&mut alice_central], case.signature_scheme()).await;
493
494 let x509_test_chain = x509_test_chain_arc.as_ref().as_ref().unwrap();
495
496 let id = conversation_id();
497 alice_central
498 .context
499 .new_conversation(&id, case.credential_type, case.cfg.clone())
500 .await
501 .unwrap();
502
503 let old_cb = alice_central
504 .find_most_recent_credential_bundle(case.signature_scheme(), case.credential_type)
505 .await
506 .unwrap()
507 .clone();
508
509 async_std::task::sleep(core::time::Duration::from_secs(1)).await;
512
513 let is_renewal = case.credential_type == MlsCredentialType::X509;
514
515 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
516 &mut alice_central,
517 &case,
518 x509_test_chain,
519 None,
520 is_renewal,
521 e2ei_utils::init_activation_or_rotation,
522 e2ei_utils::noop_restore,
523 )
524 .await
525 .unwrap();
526
527 alice_central
528 .context
529 .save_x509_credential(&mut enrollment, cert)
530 .await
531 .unwrap();
532
533 let cb = alice_central
535 .find_most_recent_credential_bundle(case.signature_scheme(), MlsCredentialType::X509)
536 .await
537 .unwrap();
538 let identity = cb
539 .to_mls_credential_with_key()
540 .extract_identity(case.ciphersuite(), None)
541 .unwrap();
542 assert_eq!(
543 identity.x509_identity.as_ref().unwrap().display_name,
544 e2ei_utils::NEW_DISPLAY_NAME
545 );
546 assert_eq!(
547 identity.x509_identity.as_ref().unwrap().handle,
548 format!("wireapp://%40{}@world.com", e2ei_utils::NEW_HANDLE)
549 );
550
551 let old_spk = SignaturePublicKey::from(old_cb.signature_key.public());
553 let old_cb_found = alice_central
554 .find_credential_bundle(case.signature_scheme(), case.credential_type, &old_spk)
555 .await
556 .unwrap();
557 assert_eq!(old_cb, old_cb_found);
558 let (cid, all_credentials, scs, old_nb_identities) = {
559 let alice_client = alice_central.session().await;
560 let old_nb_identities = alice_client.identities_count().await.unwrap();
561
562 let cid = alice_client.id().await.unwrap();
564 let scs = HashSet::from([case.signature_scheme()]);
565 let all_credentials = alice_central
566 .context
567 .keystore()
568 .await
569 .unwrap()
570 .find_all::<MlsCredential>(EntityFindParams::default())
571 .await
572 .unwrap()
573 .into_iter()
574 .map(|c| {
575 let credential =
576 openmls::prelude::Credential::tls_deserialize(&mut c.credential.as_slice())
577 .unwrap();
578 (credential, c.created_at)
579 })
580 .collect::<Vec<_>>();
581 assert_eq!(all_credentials.len(), 2);
582 (cid, all_credentials, scs, old_nb_identities)
583 };
584 let backend = &alice_central.context.mls_provider().await.unwrap();
585 backend.keystore().commit_transaction().await.unwrap();
586 backend.keystore().new_transaction().await.unwrap();
587
588 let new_client = alice_central.session.clone();
589 new_client.reset().await;
590
591 new_client.load(backend, &cid, all_credentials, scs).await.unwrap();
592
593 let cb = new_client
595 .find_most_recent_credential_bundle(case.signature_scheme(), MlsCredentialType::X509)
596 .await
597 .unwrap();
598 let identity = cb
599 .to_mls_credential_with_key()
600 .extract_identity(case.ciphersuite(), None)
601 .unwrap();
602
603 assert_eq!(
604 identity.x509_identity.as_ref().unwrap().display_name,
605 e2ei_utils::NEW_DISPLAY_NAME
606 );
607 assert_eq!(
608 identity.x509_identity.as_ref().unwrap().handle,
609 format!("wireapp://%40{}@world.com", e2ei_utils::NEW_HANDLE)
610 );
611
612 assert_eq!(new_client.identities_count().await.unwrap(), old_nb_identities);
613 })
614 })
615 .await
616 }
617
618 #[apply(all_cred_cipher)]
619 #[wasm_bindgen_test]
620 async fn rotate_should_roundtrip(case: TestCase) {
621 run_test_with_client_ids(
622 case.clone(),
623 ["alice", "bob"],
624 move |[mut alice_central, mut bob_central]| {
625 Box::pin(async move {
626 let x509_test_chain_arc = e2ei_utils::failsafe_ctx(
627 &mut [&mut alice_central, &mut bob_central],
628 case.signature_scheme(),
629 )
630 .await;
631
632 let x509_test_chain = x509_test_chain_arc.as_ref().as_ref().unwrap();
633
634 let id = conversation_id();
635 alice_central
636 .context
637 .new_conversation(&id, case.credential_type, case.cfg.clone())
638 .await
639 .unwrap();
640
641 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
642 const ALICE_NEW_HANDLE: &str = "new_alice_wire";
644 const ALICE_NEW_DISPLAY_NAME: &str = "New Alice Smith";
645
646 fn init_alice(wrapper: e2ei_utils::E2eiInitWrapper) -> e2ei_utils::InitFnReturn<'_> {
647 Box::pin(async move {
648 let e2ei_utils::E2eiInitWrapper { context: cc, case } = wrapper;
649 let cs = case.ciphersuite();
650 match case.credential_type {
651 MlsCredentialType::Basic => {
652 cc.e2ei_new_activation_enrollment(
653 ALICE_NEW_DISPLAY_NAME.to_string(),
654 ALICE_NEW_HANDLE.to_string(),
655 Some(TEAM.to_string()),
656 e2ei_utils::E2EI_EXPIRY,
657 cs,
658 )
659 .await
660 }
661 MlsCredentialType::X509 => {
662 cc.e2ei_new_rotate_enrollment(
663 Some(ALICE_NEW_DISPLAY_NAME.to_string()),
664 Some(ALICE_NEW_HANDLE.to_string()),
665 Some(TEAM.to_string()),
666 E2EI_EXPIRY,
667 cs,
668 )
669 .await
670 }
671 }
672 .map_err(RecursiveError::transaction("creating new enrollment"))
673 .map_err(Into::into)
674 })
675 }
676
677 let is_renewal = case.credential_type == MlsCredentialType::X509;
678
679 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
680 &mut alice_central,
681 &case,
682 x509_test_chain,
683 None,
684 is_renewal,
685 init_alice,
686 e2ei_utils::noop_restore,
687 )
688 .await
689 .unwrap();
690
691 alice_central
692 .context
693 .save_x509_credential(&mut enrollment, cert)
694 .await
695 .unwrap();
696 alice_central
697 .context
698 .conversation(&id)
699 .await
700 .unwrap()
701 .e2ei_rotate(None)
702 .await
703 .unwrap();
704
705 let commit = alice_central.mls_transport.latest_commit().await;
706
707 let decrypted = bob_central
708 .context
709 .conversation(&id)
710 .await
711 .unwrap()
712 .decrypt_message(commit.to_bytes().unwrap())
713 .await
714 .unwrap();
715 alice_central.verify_sender_identity(&case, &decrypted).await;
716
717 alice_central
718 .verify_local_credential_rotated(&id, ALICE_NEW_HANDLE, ALICE_NEW_DISPLAY_NAME)
719 .await;
720
721 const BOB_NEW_HANDLE: &str = "new_bob_wire";
723 const BOB_NEW_DISPLAY_NAME: &str = "New Bob Smith";
724
725 fn init_bob(wrapper: e2ei_utils::E2eiInitWrapper) -> e2ei_utils::InitFnReturn<'_> {
726 Box::pin(async move {
727 let e2ei_utils::E2eiInitWrapper { context: cc, case } = wrapper;
728 let cs = case.ciphersuite();
729 match case.credential_type {
730 MlsCredentialType::Basic => {
731 cc.e2ei_new_activation_enrollment(
732 BOB_NEW_DISPLAY_NAME.to_string(),
733 BOB_NEW_HANDLE.to_string(),
734 Some(TEAM.to_string()),
735 E2EI_EXPIRY,
736 cs,
737 )
738 .await
739 }
740 MlsCredentialType::X509 => {
741 cc.e2ei_new_rotate_enrollment(
742 Some(BOB_NEW_DISPLAY_NAME.to_string()),
743 Some(BOB_NEW_HANDLE.to_string()),
744 Some(TEAM.to_string()),
745 E2EI_EXPIRY,
746 cs,
747 )
748 .await
749 }
750 }
751 .map_err(RecursiveError::transaction("creating new enrollment"))
752 .map_err(Into::into)
753 })
754 }
755 let is_renewal = case.credential_type == MlsCredentialType::X509;
756
757 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
758 &mut bob_central,
759 &case,
760 x509_test_chain,
761 None,
762 is_renewal,
763 init_bob,
764 e2ei_utils::noop_restore,
765 )
766 .await
767 .unwrap();
768
769 bob_central
770 .context
771 .save_x509_credential(&mut enrollment, cert)
772 .await
773 .unwrap();
774
775 bob_central
776 .context
777 .conversation(&id)
778 .await
779 .unwrap()
780 .e2ei_rotate(None)
781 .await
782 .unwrap();
783
784 let commit = bob_central.mls_transport.latest_commit().await;
785
786 let decrypted = alice_central
787 .context
788 .conversation(&id)
789 .await
790 .unwrap()
791 .decrypt_message(commit.to_bytes().unwrap())
792 .await
793 .unwrap();
794 bob_central.verify_sender_identity(&case, &decrypted).await;
795
796 bob_central
797 .verify_local_credential_rotated(&id, BOB_NEW_HANDLE, BOB_NEW_DISPLAY_NAME)
798 .await;
799 })
800 },
801 )
802 .await
803 }
804 }
805
806 mod one {
807 use super::*;
808 use crate::mls::conversation::Conversation as _;
809
810 #[apply(all_cred_cipher)]
811 #[wasm_bindgen_test]
812 pub async fn should_rotate_one_conversations_credential(case: TestCase) {
813 if case.is_x509() {
814 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
815 Box::pin(async move {
816 let id = conversation_id();
817 alice_central
818 .context
819 .new_conversation(&id, case.credential_type, case.cfg.clone())
820 .await
821 .unwrap();
822
823 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
824
825 let init_count = alice_central.context.count_entities().await;
826 let x509_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
827
828 let intermediate_ca = x509_test_chain.find_local_intermediate_ca();
829 let alice_og_cert = &x509_test_chain
830 .actors
831 .iter()
832 .find(|actor| actor.name == "alice")
833 .unwrap()
834 .certificate;
835
836 let alice_cid = alice_central.get_client_id().await;
838 let (new_handle, new_display_name) = ("new_alice_wire", "New Alice Smith");
839 let cb = alice_central
840 .save_new_credential(&case, new_handle, new_display_name, alice_og_cert, intermediate_ca)
841 .await;
842
843 let alice_old_identities = alice_central
845 .context
846 .conversation(&id)
847 .await
848 .unwrap()
849 .get_device_identities(&[alice_cid])
850 .await
851 .unwrap();
852 let alice_old_identity = alice_old_identities.first().unwrap();
853 assert_ne!(
854 alice_old_identity.x509_identity.as_ref().unwrap().display_name,
855 new_display_name
856 );
857 assert_ne!(
858 alice_old_identity.x509_identity.as_ref().unwrap().handle,
859 format!("{new_handle}@world.com")
860 );
861
862 alice_central
864 .context
865 .conversation(&id)
866 .await
867 .unwrap()
868 .e2ei_rotate(Some(&cb))
869 .await
870 .unwrap();
871 let commit = alice_central.mls_transport.latest_commit().await;
872
873 let decrypted = bob_central
875 .context
876 .conversation(&id)
877 .await
878 .unwrap()
879 .decrypt_message(commit.to_bytes().unwrap())
880 .await
881 .unwrap();
882 alice_central.verify_sender_identity(&case, &decrypted).await;
884
885 alice_central
887 .verify_local_credential_rotated(&id, new_handle, new_display_name)
888 .await;
889
890 let final_count = alice_central.context.count_entities().await;
891 assert_eq!(init_count.encryption_keypair, final_count.encryption_keypair);
892 assert_eq!(
893 init_count.epoch_encryption_keypair,
894 final_count.epoch_encryption_keypair
895 );
896 assert_eq!(init_count.key_package, final_count.key_package);
897 })
898 })
899 .await
900 }
901 }
902
903 #[apply(all_cred_cipher)]
904 #[wasm_bindgen_test]
905 pub async fn rotate_should_be_renewable_when_commit_denied(case: TestCase) {
906 if !case.is_x509() {
907 return;
908 }
909 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
910 Box::pin(async move {
911 let id = conversation_id();
912 alice_central
913 .context
914 .new_conversation(&id, case.credential_type, case.cfg.clone())
915 .await
916 .unwrap();
917
918 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
919
920 let init_count = alice_central.context.count_entities().await;
921
922 let x509_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
923
924 let intermediate_ca = x509_test_chain.find_local_intermediate_ca();
925
926 let (new_handle, new_display_name) = ("new_alice_wire", "New Alice Smith");
931 let cb = alice_central
932 .save_new_credential(
933 &case,
934 new_handle,
935 new_display_name,
936 x509_test_chain.find_certificate_for_actor("alice").unwrap(),
937 intermediate_ca,
938 )
939 .await;
940
941 let _rotate_commit = alice_central.create_unmerged_e2ei_rotate_commit(&id, &cb).await;
943
944 bob_central
946 .context
947 .conversation(&id)
948 .await
949 .unwrap()
950 .update_key_material()
951 .await
952 .unwrap();
953 let bob_commit = bob_central.mls_transport.latest_commit().await;
955
956 let decrypted = alice_central
958 .context
959 .conversation(&id)
960 .await
961 .unwrap()
962 .decrypt_message(bob_commit.to_bytes().unwrap())
963 .await
964 .unwrap();
965
966 assert_eq!(decrypted.proposals.len(), 1);
968 let renewed_proposal = decrypted.proposals.first().unwrap();
969 bob_central
970 .context
971 .conversation(&id)
972 .await
973 .unwrap()
974 .decrypt_message(renewed_proposal.proposal.to_bytes().unwrap())
975 .await
976 .unwrap();
977
978 alice_central
979 .context
980 .conversation(&id)
981 .await
982 .unwrap()
983 .commit_pending_proposals()
984 .await
985 .unwrap();
986
987 alice_central
989 .verify_local_credential_rotated(&id, new_handle, new_display_name)
990 .await;
991
992 let rotate_commit = alice_central.mls_transport.latest_commit().await;
993 let decrypted = bob_central
995 .context
996 .conversation(&id)
997 .await
998 .unwrap()
999 .decrypt_message(rotate_commit.to_bytes().unwrap())
1000 .await
1001 .unwrap();
1002 alice_central.verify_sender_identity(&case, &decrypted).await;
1003
1004 let final_count = alice_central.context.count_entities().await;
1005 assert_eq!(init_count.encryption_keypair, final_count.encryption_keypair);
1006 })
1012 })
1013 .await
1014 }
1015
1016 #[apply(all_cred_cipher)]
1017 #[wasm_bindgen_test]
1018 pub async fn rotate_should_replace_existing_basic_credentials(case: TestCase) {
1019 if case.is_x509() {
1020 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
1021 Box::pin(async move {
1022 let id = conversation_id();
1023 alice_central
1024 .context
1025 .new_conversation(&id, MlsCredentialType::Basic, case.cfg.clone())
1026 .await
1027 .unwrap();
1028
1029 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
1030
1031 let x509_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
1032 let intermediate_ca = x509_test_chain.find_local_intermediate_ca();
1033 let alice_og_cert = &x509_test_chain
1034 .actors
1035 .iter()
1036 .find(|actor| actor.name == "alice")
1037 .unwrap()
1038 .certificate;
1039
1040 let alice_cid = alice_central.get_client_id().await;
1042 let (new_handle, new_display_name) = ("new_alice_wire", "New Alice Smith");
1043 alice_central
1044 .save_new_credential(&case, new_handle, new_display_name, alice_og_cert, intermediate_ca)
1045 .await;
1046
1047 let alice_old_identities = alice_central
1049 .context
1050 .conversation(&id)
1051 .await
1052 .unwrap()
1053 .get_device_identities(&[alice_cid])
1054 .await
1055 .unwrap();
1056 let alice_old_identity = alice_old_identities.first().unwrap();
1057 assert_eq!(alice_old_identity.credential_type, MlsCredentialType::Basic);
1058 assert_eq!(alice_old_identity.x509_identity, None);
1059
1060 alice_central
1062 .context
1063 .conversation(&id)
1064 .await
1065 .unwrap()
1066 .e2ei_rotate(None)
1067 .await
1068 .unwrap();
1069 let commit = alice_central.mls_transport.latest_commit().await;
1070
1071 let decrypted = bob_central
1073 .context
1074 .conversation(&id)
1075 .await
1076 .unwrap()
1077 .decrypt_message(commit.to_bytes().unwrap())
1078 .await
1079 .unwrap();
1080 alice_central.verify_sender_identity(&case, &decrypted).await;
1082
1083 alice_central
1085 .verify_local_credential_rotated(&id, new_handle, new_display_name)
1086 .await;
1087 })
1088 })
1089 .await
1090 }
1091 }
1092 }
1093}