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.extract_dp_on_init(&certificate_chain[..]).await?;
167
168 let cert_bundle = CertificateBundle {
169 certificate_chain,
170 private_key,
171 };
172 let client = &self
173 .session()
174 .await
175 .map_err(RecursiveError::transaction("getting mls provider"))?;
176
177 client
178 .save_new_x509_credential_bundle(
179 &self
180 .mls_provider()
181 .await
182 .map_err(RecursiveError::transaction("getting mls provider"))?
183 .keystore(),
184 cs.signature_algorithm(),
185 cert_bundle,
186 )
187 .await
188 .map_err(RecursiveError::mls_client("saving new x509 credential bundle"))?;
189
190 Ok(crl_new_distribution_points)
191 }
192
193 pub async fn delete_stale_key_packages(&self, cipher_suite: MlsCiphersuite) -> Result<()> {
196 let signature_scheme = cipher_suite.signature_algorithm();
197 let keystore = self
198 .keystore()
199 .await
200 .map_err(RecursiveError::transaction("getting keystore"))?;
201 let nb_kp = keystore
202 .count::<MlsKeyPackage>()
203 .await
204 .map_err(KeystoreError::wrap("counting key packages"))?;
205 let kps: Vec<KeyPackage> = keystore
206 .mls_fetch_keypackages(nb_kp as u32)
207 .await
208 .map_err(KeystoreError::wrap("fetching key packages"))?;
209 let client = self
210 .session()
211 .await
212 .map_err(RecursiveError::transaction("getting mls client"))?;
213
214 let cb = client
215 .find_most_recent_credential_bundle(signature_scheme, MlsCredentialType::X509)
216 .await
217 .map_err(RecursiveError::mls_client("finding most recent credential bundle"))?;
218
219 let mut kp_refs = vec![];
220
221 let provider = self
222 .mls_provider()
223 .await
224 .map_err(RecursiveError::transaction("getting mls provider"))?;
225 for kp in kps {
226 let kp_cred = kp.leaf_node().credential().mls_credential();
227 let local_cred = cb.credential().mls_credential();
228 if kp_cred != local_cred {
229 let kpr = kp
230 .hash_ref(provider.crypto())
231 .map_err(MlsError::wrap("computing keypackage hashref"))?;
232 kp_refs.push(kpr);
233 };
234 }
235 self.delete_keypackages(&kp_refs)
236 .await
237 .map_err(RecursiveError::transaction("deleting keypackages"))?;
238 Ok(())
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::{
246 e2e_identity::enrollment::test_utils as e2ei_utils, mls::credential::ext::CredentialExt,
247 prelude::key_package::INITIAL_KEYING_MATERIAL_COUNT, test_utils::*,
248 };
249 use core_crypto_keystore::entities::{EntityFindParams, MlsCredential};
250 use openmls::prelude::SignaturePublicKey;
251 use std::collections::HashSet;
252 use tls_codec::Deserialize;
253 use wasm_bindgen_test::*;
254
255 wasm_bindgen_test_configure!(run_in_browser);
256
257 pub(crate) mod all {
258 use e2ei_utils::E2EI_EXPIRY;
259
260 use super::*;
261 use crate::test_utils::context::TEAM;
262
263 #[apply(all_cred_cipher)]
264 #[wasm_bindgen_test]
265 async fn enrollment_should_rotate_all(case: TestContext) {
266 run_test_with_client_ids(
267 case.clone(),
268 ["alice", "bob", "charlie"],
269 move |[mut alice_central, mut bob_central, mut charlie_central]| {
270 Box::pin(async move {
271 const N: usize = 50;
272 const NB_KEY_PACKAGE: usize = 50;
273
274 let mut ids = vec![];
275
276 let x509_test_chain_arc = e2ei_utils::failsafe_ctx(
277 &mut [&mut alice_central, &mut bob_central, &mut charlie_central],
278 case.signature_scheme(),
279 )
280 .await;
281
282 let x509_test_chain = x509_test_chain_arc.as_ref().as_ref().unwrap();
283
284 for _ in 0..N {
285 let id = conversation_id();
286 alice_central
287 .transaction
288 .new_conversation(&id, case.credential_type, case.cfg.clone())
289 .await
290 .unwrap();
291 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
292 ids.push(id)
293 }
294
295 let before_rotate = alice_central.transaction.count_entities().await;
297 assert_eq!(before_rotate.key_package, INITIAL_KEYING_MATERIAL_COUNT);
298
299 assert_eq!(before_rotate.hpke_private_key, INITIAL_KEYING_MATERIAL_COUNT);
300
301 assert_eq!(before_rotate.encryption_keypair, INITIAL_KEYING_MATERIAL_COUNT);
303
304 assert_eq!(before_rotate.credential, 1);
305 let old_credential = alice_central
306 .find_most_recent_credential_bundle(case.signature_scheme(), case.credential_type)
307 .await
308 .unwrap()
309 .clone();
310
311 let is_renewal = case.credential_type == MlsCredentialType::X509;
312
313 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
314 &mut alice_central,
315 &case,
316 x509_test_chain,
317 None,
318 is_renewal,
319 e2ei_utils::init_activation_or_rotation,
320 e2ei_utils::noop_restore,
321 )
322 .await
323 .unwrap();
324
325 alice_central
326 .transaction
327 .save_x509_credential(&mut enrollment, cert)
328 .await
329 .unwrap();
330
331 let cb = alice_central
332 .find_most_recent_credential_bundle(case.signature_scheme(), MlsCredentialType::X509)
333 .await
334 .unwrap();
335
336 let result = alice_central
337 .create_key_packages_and_update_credential_in_all_conversations(
338 &cb,
339 *enrollment.ciphersuite(),
340 NB_KEY_PACKAGE,
341 )
342 .await
343 .unwrap();
344
345 let after_rotate = alice_central.transaction.count_entities().await;
346 assert_eq!(after_rotate.key_package - before_rotate.key_package, NB_KEY_PACKAGE);
348
349 assert_eq!(after_rotate.credential - before_rotate.credential, 1);
351
352 for (id, commit) in result.conversation_ids_and_commits.into_iter() {
353 let decrypted = bob_central
354 .transaction
355 .conversation(&id)
356 .await
357 .unwrap()
358 .decrypt_message(commit.commit.to_bytes().unwrap())
359 .await
360 .unwrap();
361 alice_central.verify_sender_identity(&case, &decrypted).await;
362
363 alice_central
364 .verify_local_credential_rotated(
365 &id,
366 e2ei_utils::NEW_HANDLE,
367 e2ei_utils::NEW_DISPLAY_NAME,
368 )
369 .await;
370 }
371
372 let new_credentials = result
374 .new_key_packages
375 .iter()
376 .map(|kp| kp.leaf_node().to_credential_with_key());
377 for c in new_credentials {
378 assert_eq!(c.credential.credential_type(), openmls::prelude::CredentialType::X509);
379 let identity = c.extract_identity(case.ciphersuite(), None).unwrap();
380 assert_eq!(
381 identity.x509_identity.as_ref().unwrap().display_name,
382 e2ei_utils::NEW_DISPLAY_NAME
383 );
384 assert_eq!(
385 identity.x509_identity.as_ref().unwrap().handle,
386 format!("wireapp://%40{}@world.com", e2ei_utils::NEW_HANDLE)
387 );
388 }
389
390 assert!(
394 alice_central
395 .find_credential_bundle(
396 case.signature_scheme(),
397 case.credential_type,
398 &old_credential.signature_key.public().into()
399 )
400 .await
401 .is_some()
402 );
403
404 let before_delete = alice_central.transaction.count_entities().await;
406 assert_eq!(
407 before_delete.hpke_private_key - before_rotate.hpke_private_key,
408 NB_KEY_PACKAGE
409 );
410
411 assert_eq!(before_delete.key_package - before_rotate.key_package, NB_KEY_PACKAGE);
413
414 assert!(
416 alice_central
417 .find_signature_keypair_from_keystore(old_credential.signature_key.public())
418 .await
419 .is_some()
420 );
421
422 alice_central
425 .transaction
426 .delete_stale_key_packages(case.ciphersuite())
427 .await
428 .unwrap();
429
430 let nb_x509_kp = alice_central
432 .count_key_package(case.ciphersuite(), Some(MlsCredentialType::X509))
433 .await;
434 assert_eq!(nb_x509_kp, NB_KEY_PACKAGE);
435 let nb_basic_kp = alice_central
437 .count_key_package(case.ciphersuite(), Some(MlsCredentialType::Basic))
438 .await;
439 assert_eq!(nb_basic_kp, 0);
440
441 let after_delete = alice_central.transaction.count_entities().await;
445 assert_eq!(after_delete.credential, 1);
446 assert!(
447 alice_central
448 .find_credential_from_keystore(&old_credential)
449 .await
450 .is_none()
451 );
452
453 assert_eq!(after_delete.hpke_private_key, NB_KEY_PACKAGE);
455
456 assert_eq!(
458 after_rotate.encryption_keypair - after_delete.encryption_keypair,
459 INITIAL_KEYING_MATERIAL_COUNT
460 );
461
462 let id = conversation_id();
464 charlie_central
465 .transaction
466 .new_conversation(&id, case.credential_type, case.cfg.clone())
467 .await
468 .unwrap();
469 let alice = alice_central
471 .rand_key_package_of_type(&case, MlsCredentialType::X509)
472 .await;
473 charlie_central
474 .invite_all_members(&case, &id, [(&alice_central, alice)])
475 .await
476 .unwrap();
477 })
478 },
479 )
480 .await
481 }
482
483 #[apply(all_cred_cipher)]
484 #[wasm_bindgen_test]
485 async fn should_restore_credentials_in_order(case: TestContext) {
486 run_test_with_client_ids(case.clone(), ["alice"], move |[mut alice_central]| {
487 Box::pin(async move {
488 let x509_test_chain_arc =
489 e2ei_utils::failsafe_ctx(&mut [&mut alice_central], case.signature_scheme()).await;
490
491 let x509_test_chain = x509_test_chain_arc.as_ref().as_ref().unwrap();
492
493 let id = conversation_id();
494 alice_central
495 .transaction
496 .new_conversation(&id, case.credential_type, case.cfg.clone())
497 .await
498 .unwrap();
499
500 let old_cb = alice_central
501 .find_most_recent_credential_bundle(case.signature_scheme(), case.credential_type)
502 .await
503 .unwrap()
504 .clone();
505
506 async_std::task::sleep(core::time::Duration::from_secs(1)).await;
509
510 let is_renewal = case.credential_type == MlsCredentialType::X509;
511
512 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
513 &mut alice_central,
514 &case,
515 x509_test_chain,
516 None,
517 is_renewal,
518 e2ei_utils::init_activation_or_rotation,
519 e2ei_utils::noop_restore,
520 )
521 .await
522 .unwrap();
523
524 alice_central
525 .transaction
526 .save_x509_credential(&mut enrollment, cert)
527 .await
528 .unwrap();
529
530 let cb = alice_central
532 .find_most_recent_credential_bundle(case.signature_scheme(), MlsCredentialType::X509)
533 .await
534 .unwrap();
535 let identity = cb
536 .to_mls_credential_with_key()
537 .extract_identity(case.ciphersuite(), None)
538 .unwrap();
539 assert_eq!(
540 identity.x509_identity.as_ref().unwrap().display_name,
541 e2ei_utils::NEW_DISPLAY_NAME
542 );
543 assert_eq!(
544 identity.x509_identity.as_ref().unwrap().handle,
545 format!("wireapp://%40{}@world.com", e2ei_utils::NEW_HANDLE)
546 );
547
548 let old_spk = SignaturePublicKey::from(old_cb.signature_key.public());
550 let old_cb_found = alice_central
551 .find_credential_bundle(case.signature_scheme(), case.credential_type, &old_spk)
552 .await
553 .unwrap();
554 assert_eq!(old_cb, old_cb_found);
555 let (cid, all_credentials, scs, old_nb_identities) = {
556 let alice_client = alice_central.session().await;
557 let old_nb_identities = alice_client.identities_count().await.unwrap();
558
559 let cid = alice_client.id().await.unwrap();
561 let scs = HashSet::from([case.signature_scheme()]);
562 let all_credentials = alice_central
563 .transaction
564 .keystore()
565 .await
566 .unwrap()
567 .find_all::<MlsCredential>(EntityFindParams::default())
568 .await
569 .unwrap()
570 .into_iter()
571 .map(|c| {
572 let credential =
573 openmls::prelude::Credential::tls_deserialize(&mut c.credential.as_slice())
574 .unwrap();
575 (credential, c.created_at)
576 })
577 .collect::<Vec<_>>();
578 assert_eq!(all_credentials.len(), 2);
579 (cid, all_credentials, scs, old_nb_identities)
580 };
581 let backend = &alice_central.transaction.mls_provider().await.unwrap();
582 backend.keystore().commit_transaction().await.unwrap();
583 backend.keystore().new_transaction().await.unwrap();
584
585 let new_client = alice_central.session.clone();
586 new_client.reset().await;
587
588 new_client.load(backend, &cid, all_credentials, scs).await.unwrap();
589
590 let cb = new_client
592 .find_most_recent_credential_bundle(case.signature_scheme(), MlsCredentialType::X509)
593 .await
594 .unwrap();
595 let identity = cb
596 .to_mls_credential_with_key()
597 .extract_identity(case.ciphersuite(), None)
598 .unwrap();
599
600 assert_eq!(
601 identity.x509_identity.as_ref().unwrap().display_name,
602 e2ei_utils::NEW_DISPLAY_NAME
603 );
604 assert_eq!(
605 identity.x509_identity.as_ref().unwrap().handle,
606 format!("wireapp://%40{}@world.com", e2ei_utils::NEW_HANDLE)
607 );
608
609 assert_eq!(new_client.identities_count().await.unwrap(), old_nb_identities);
610 })
611 })
612 .await
613 }
614
615 #[apply(all_cred_cipher)]
616 #[wasm_bindgen_test]
617 async fn rotate_should_roundtrip(case: TestContext) {
618 run_test_with_client_ids(
619 case.clone(),
620 ["alice", "bob"],
621 move |[mut alice_central, mut bob_central]| {
622 Box::pin(async move {
623 let x509_test_chain_arc = e2ei_utils::failsafe_ctx(
624 &mut [&mut alice_central, &mut bob_central],
625 case.signature_scheme(),
626 )
627 .await;
628
629 let x509_test_chain = x509_test_chain_arc.as_ref().as_ref().unwrap();
630
631 let id = conversation_id();
632 alice_central
633 .transaction
634 .new_conversation(&id, case.credential_type, case.cfg.clone())
635 .await
636 .unwrap();
637
638 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
639 const ALICE_NEW_HANDLE: &str = "new_alice_wire";
641 const ALICE_NEW_DISPLAY_NAME: &str = "New Alice Smith";
642
643 fn init_alice(wrapper: e2ei_utils::E2eiInitWrapper) -> e2ei_utils::InitFnReturn<'_> {
644 Box::pin(async move {
645 let e2ei_utils::E2eiInitWrapper { context: cc, case } = wrapper;
646 let cs = case.ciphersuite();
647 match case.credential_type {
648 MlsCredentialType::Basic => {
649 cc.e2ei_new_activation_enrollment(
650 ALICE_NEW_DISPLAY_NAME.to_string(),
651 ALICE_NEW_HANDLE.to_string(),
652 Some(TEAM.to_string()),
653 e2ei_utils::E2EI_EXPIRY,
654 cs,
655 )
656 .await
657 }
658 MlsCredentialType::X509 => {
659 cc.e2ei_new_rotate_enrollment(
660 Some(ALICE_NEW_DISPLAY_NAME.to_string()),
661 Some(ALICE_NEW_HANDLE.to_string()),
662 Some(TEAM.to_string()),
663 E2EI_EXPIRY,
664 cs,
665 )
666 .await
667 }
668 }
669 .map_err(RecursiveError::transaction("creating new enrollment"))
670 .map_err(Into::into)
671 })
672 }
673
674 let is_renewal = case.credential_type == MlsCredentialType::X509;
675
676 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
677 &mut alice_central,
678 &case,
679 x509_test_chain,
680 None,
681 is_renewal,
682 init_alice,
683 e2ei_utils::noop_restore,
684 )
685 .await
686 .unwrap();
687
688 alice_central
689 .transaction
690 .save_x509_credential(&mut enrollment, cert)
691 .await
692 .unwrap();
693 alice_central
694 .transaction
695 .conversation(&id)
696 .await
697 .unwrap()
698 .e2ei_rotate(None)
699 .await
700 .unwrap();
701
702 let commit = alice_central.mls_transport.latest_commit().await;
703
704 let decrypted = bob_central
705 .transaction
706 .conversation(&id)
707 .await
708 .unwrap()
709 .decrypt_message(commit.to_bytes().unwrap())
710 .await
711 .unwrap();
712 alice_central.verify_sender_identity(&case, &decrypted).await;
713
714 alice_central
715 .verify_local_credential_rotated(&id, ALICE_NEW_HANDLE, ALICE_NEW_DISPLAY_NAME)
716 .await;
717
718 const BOB_NEW_HANDLE: &str = "new_bob_wire";
720 const BOB_NEW_DISPLAY_NAME: &str = "New Bob Smith";
721
722 fn init_bob(wrapper: e2ei_utils::E2eiInitWrapper) -> e2ei_utils::InitFnReturn<'_> {
723 Box::pin(async move {
724 let e2ei_utils::E2eiInitWrapper { context: cc, case } = wrapper;
725 let cs = case.ciphersuite();
726 match case.credential_type {
727 MlsCredentialType::Basic => {
728 cc.e2ei_new_activation_enrollment(
729 BOB_NEW_DISPLAY_NAME.to_string(),
730 BOB_NEW_HANDLE.to_string(),
731 Some(TEAM.to_string()),
732 E2EI_EXPIRY,
733 cs,
734 )
735 .await
736 }
737 MlsCredentialType::X509 => {
738 cc.e2ei_new_rotate_enrollment(
739 Some(BOB_NEW_DISPLAY_NAME.to_string()),
740 Some(BOB_NEW_HANDLE.to_string()),
741 Some(TEAM.to_string()),
742 E2EI_EXPIRY,
743 cs,
744 )
745 .await
746 }
747 }
748 .map_err(RecursiveError::transaction("creating new enrollment"))
749 .map_err(Into::into)
750 })
751 }
752 let is_renewal = case.credential_type == MlsCredentialType::X509;
753
754 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
755 &mut bob_central,
756 &case,
757 x509_test_chain,
758 None,
759 is_renewal,
760 init_bob,
761 e2ei_utils::noop_restore,
762 )
763 .await
764 .unwrap();
765
766 bob_central
767 .transaction
768 .save_x509_credential(&mut enrollment, cert)
769 .await
770 .unwrap();
771
772 bob_central
773 .transaction
774 .conversation(&id)
775 .await
776 .unwrap()
777 .e2ei_rotate(None)
778 .await
779 .unwrap();
780
781 let commit = bob_central.mls_transport.latest_commit().await;
782
783 let decrypted = alice_central
784 .transaction
785 .conversation(&id)
786 .await
787 .unwrap()
788 .decrypt_message(commit.to_bytes().unwrap())
789 .await
790 .unwrap();
791 bob_central.verify_sender_identity(&case, &decrypted).await;
792
793 bob_central
794 .verify_local_credential_rotated(&id, BOB_NEW_HANDLE, BOB_NEW_DISPLAY_NAME)
795 .await;
796 })
797 },
798 )
799 .await
800 }
801 }
802
803 mod one {
804 use super::*;
805 use crate::mls::conversation::Conversation as _;
806
807 #[apply(all_cred_cipher)]
808 #[wasm_bindgen_test]
809 pub async fn should_rotate_one_conversations_credential(case: TestContext) {
810 if case.is_x509() {
811 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
812 Box::pin(async move {
813 let id = conversation_id();
814 alice_central
815 .transaction
816 .new_conversation(&id, case.credential_type, case.cfg.clone())
817 .await
818 .unwrap();
819
820 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
821
822 let init_count = alice_central.transaction.count_entities().await;
823 let x509_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
824
825 let intermediate_ca = x509_test_chain.find_local_intermediate_ca();
826 let alice_og_cert = &x509_test_chain
827 .actors
828 .iter()
829 .find(|actor| actor.name == "alice")
830 .unwrap()
831 .certificate;
832
833 let alice_cid = alice_central.get_client_id().await;
835 let (new_handle, new_display_name) = ("new_alice_wire", "New Alice Smith");
836 let cb = alice_central
837 .save_new_credential(&case, new_handle, new_display_name, alice_og_cert, intermediate_ca)
838 .await;
839
840 let alice_old_identities = alice_central
842 .transaction
843 .conversation(&id)
844 .await
845 .unwrap()
846 .get_device_identities(&[alice_cid])
847 .await
848 .unwrap();
849 let alice_old_identity = alice_old_identities.first().unwrap();
850 assert_ne!(
851 alice_old_identity.x509_identity.as_ref().unwrap().display_name,
852 new_display_name
853 );
854 assert_ne!(
855 alice_old_identity.x509_identity.as_ref().unwrap().handle,
856 format!("{new_handle}@world.com")
857 );
858
859 alice_central
861 .transaction
862 .conversation(&id)
863 .await
864 .unwrap()
865 .e2ei_rotate(Some(&cb))
866 .await
867 .unwrap();
868 let commit = alice_central.mls_transport.latest_commit().await;
869
870 let decrypted = bob_central
872 .transaction
873 .conversation(&id)
874 .await
875 .unwrap()
876 .decrypt_message(commit.to_bytes().unwrap())
877 .await
878 .unwrap();
879 alice_central.verify_sender_identity(&case, &decrypted).await;
881
882 alice_central
884 .verify_local_credential_rotated(&id, new_handle, new_display_name)
885 .await;
886
887 let final_count = alice_central.transaction.count_entities().await;
888 assert_eq!(init_count.encryption_keypair, final_count.encryption_keypair);
889 assert_eq!(
890 init_count.epoch_encryption_keypair,
891 final_count.epoch_encryption_keypair
892 );
893 assert_eq!(init_count.key_package, final_count.key_package);
894 })
895 })
896 .await
897 }
898 }
899
900 #[apply(all_cred_cipher)]
901 #[wasm_bindgen_test]
902 pub async fn rotate_should_be_renewable_when_commit_denied(case: TestContext) {
903 if !case.is_x509() {
904 return;
905 }
906 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
907 Box::pin(async move {
908 let id = conversation_id();
909 alice_central
910 .transaction
911 .new_conversation(&id, case.credential_type, case.cfg.clone())
912 .await
913 .unwrap();
914
915 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
916
917 let init_count = alice_central.transaction.count_entities().await;
918
919 let x509_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
920
921 let intermediate_ca = x509_test_chain.find_local_intermediate_ca();
922
923 let (new_handle, new_display_name) = ("new_alice_wire", "New Alice Smith");
928 let cb = alice_central
929 .save_new_credential(
930 &case,
931 new_handle,
932 new_display_name,
933 x509_test_chain.find_certificate_for_actor("alice").unwrap(),
934 intermediate_ca,
935 )
936 .await;
937
938 let _rotate_commit = alice_central.create_unmerged_e2ei_rotate_commit(&id, &cb).await;
940
941 bob_central
943 .transaction
944 .conversation(&id)
945 .await
946 .unwrap()
947 .update_key_material()
948 .await
949 .unwrap();
950 let bob_commit = bob_central.mls_transport.latest_commit().await;
952
953 let decrypted = alice_central
955 .transaction
956 .conversation(&id)
957 .await
958 .unwrap()
959 .decrypt_message(bob_commit.to_bytes().unwrap())
960 .await
961 .unwrap();
962
963 assert_eq!(decrypted.proposals.len(), 1);
965 let renewed_proposal = decrypted.proposals.first().unwrap();
966 bob_central
967 .transaction
968 .conversation(&id)
969 .await
970 .unwrap()
971 .decrypt_message(renewed_proposal.proposal.to_bytes().unwrap())
972 .await
973 .unwrap();
974
975 alice_central
976 .transaction
977 .conversation(&id)
978 .await
979 .unwrap()
980 .commit_pending_proposals()
981 .await
982 .unwrap();
983
984 alice_central
986 .verify_local_credential_rotated(&id, new_handle, new_display_name)
987 .await;
988
989 let rotate_commit = alice_central.mls_transport.latest_commit().await;
990 let decrypted = bob_central
992 .transaction
993 .conversation(&id)
994 .await
995 .unwrap()
996 .decrypt_message(rotate_commit.to_bytes().unwrap())
997 .await
998 .unwrap();
999 alice_central.verify_sender_identity(&case, &decrypted).await;
1000
1001 let final_count = alice_central.transaction.count_entities().await;
1002 assert_eq!(init_count.encryption_keypair, final_count.encryption_keypair);
1003 })
1009 })
1010 .await
1011 }
1012
1013 #[apply(all_cred_cipher)]
1014 #[wasm_bindgen_test]
1015 pub async fn rotate_should_replace_existing_basic_credentials(case: TestContext) {
1016 if case.is_x509() {
1017 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
1018 Box::pin(async move {
1019 let id = conversation_id();
1020 alice_central
1021 .transaction
1022 .new_conversation(&id, MlsCredentialType::Basic, case.cfg.clone())
1023 .await
1024 .unwrap();
1025
1026 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
1027
1028 let x509_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
1029 let intermediate_ca = x509_test_chain.find_local_intermediate_ca();
1030 let alice_og_cert = &x509_test_chain
1031 .actors
1032 .iter()
1033 .find(|actor| actor.name == "alice")
1034 .unwrap()
1035 .certificate;
1036
1037 let alice_cid = alice_central.get_client_id().await;
1039 let (new_handle, new_display_name) = ("new_alice_wire", "New Alice Smith");
1040 alice_central
1041 .save_new_credential(&case, new_handle, new_display_name, alice_og_cert, intermediate_ca)
1042 .await;
1043
1044 let alice_old_identities = alice_central
1046 .transaction
1047 .conversation(&id)
1048 .await
1049 .unwrap()
1050 .get_device_identities(&[alice_cid])
1051 .await
1052 .unwrap();
1053 let alice_old_identity = alice_old_identities.first().unwrap();
1054 assert_eq!(alice_old_identity.credential_type, MlsCredentialType::Basic);
1055 assert_eq!(alice_old_identity.x509_identity, None);
1056
1057 alice_central
1059 .transaction
1060 .conversation(&id)
1061 .await
1062 .unwrap()
1063 .e2ei_rotate(None)
1064 .await
1065 .unwrap();
1066 let commit = alice_central.mls_transport.latest_commit().await;
1067
1068 let decrypted = bob_central
1070 .transaction
1071 .conversation(&id)
1072 .await
1073 .unwrap()
1074 .decrypt_message(commit.to_bytes().unwrap())
1075 .await
1076 .unwrap();
1077 alice_central.verify_sender_identity(&case, &decrypted).await;
1079
1080 alice_central
1082 .verify_local_credential_rotated(&id, new_handle, new_display_name)
1083 .await;
1084 })
1085 })
1086 .await
1087 }
1088 }
1089 }
1090}