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