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 let [mut alice_central, mut bob_central, mut charlie_central] = case.sessions().await;
267 Box::pin(async move {
268 const N: usize = 50;
269 const NB_KEY_PACKAGE: usize = 50;
270
271 let mut ids = vec![];
272
273 let x509_test_chain_arc = e2ei_utils::failsafe_ctx(
274 &mut [&mut alice_central, &mut bob_central, &mut charlie_central],
275 case.signature_scheme(),
276 )
277 .await;
278
279 let x509_test_chain = x509_test_chain_arc.as_ref().as_ref().unwrap();
280
281 for _ in 0..N {
282 let id = conversation_id();
283 alice_central
284 .transaction
285 .new_conversation(&id, case.credential_type, case.cfg.clone())
286 .await
287 .unwrap();
288 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
289 ids.push(id)
290 }
291
292 let before_rotate = alice_central.transaction.count_entities().await;
294 assert_eq!(before_rotate.key_package, INITIAL_KEYING_MATERIAL_COUNT);
295
296 assert_eq!(before_rotate.hpke_private_key, INITIAL_KEYING_MATERIAL_COUNT);
297
298 assert_eq!(before_rotate.encryption_keypair, INITIAL_KEYING_MATERIAL_COUNT);
300
301 assert_eq!(before_rotate.credential, 1);
302 let old_credential = alice_central
303 .find_most_recent_credential_bundle(case.signature_scheme(), case.credential_type)
304 .await
305 .unwrap()
306 .clone();
307
308 let is_renewal = case.credential_type == MlsCredentialType::X509;
309
310 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
311 &mut alice_central,
312 &case,
313 x509_test_chain,
314 None,
315 is_renewal,
316 e2ei_utils::init_activation_or_rotation,
317 e2ei_utils::noop_restore,
318 )
319 .await
320 .unwrap();
321
322 alice_central
323 .transaction
324 .save_x509_credential(&mut enrollment, cert)
325 .await
326 .unwrap();
327
328 let cb = alice_central
329 .find_most_recent_credential_bundle(case.signature_scheme(), MlsCredentialType::X509)
330 .await
331 .unwrap();
332
333 let result = alice_central
334 .create_key_packages_and_update_credential_in_all_conversations(
335 &cb,
336 *enrollment.ciphersuite(),
337 NB_KEY_PACKAGE,
338 )
339 .await
340 .unwrap();
341
342 let after_rotate = alice_central.transaction.count_entities().await;
343 assert_eq!(after_rotate.key_package - before_rotate.key_package, NB_KEY_PACKAGE);
345
346 assert_eq!(after_rotate.credential - before_rotate.credential, 1);
348
349 for (id, commit) in result.conversation_ids_and_commits.into_iter() {
350 let decrypted = bob_central
351 .transaction
352 .conversation(&id)
353 .await
354 .unwrap()
355 .decrypt_message(commit.commit.to_bytes().unwrap())
356 .await
357 .unwrap();
358 alice_central.verify_sender_identity(&case, &decrypted).await;
359
360 alice_central
361 .verify_local_credential_rotated(&id, e2ei_utils::NEW_HANDLE, e2ei_utils::NEW_DISPLAY_NAME)
362 .await;
363 }
364
365 let new_credentials = result
367 .new_key_packages
368 .iter()
369 .map(|kp| kp.leaf_node().to_credential_with_key());
370 for c in new_credentials {
371 assert_eq!(c.credential.credential_type(), openmls::prelude::CredentialType::X509);
372 let identity = c.extract_identity(case.ciphersuite(), None).unwrap();
373 assert_eq!(
374 identity.x509_identity.as_ref().unwrap().display_name,
375 e2ei_utils::NEW_DISPLAY_NAME
376 );
377 assert_eq!(
378 identity.x509_identity.as_ref().unwrap().handle,
379 format!("wireapp://%40{}@world.com", e2ei_utils::NEW_HANDLE)
380 );
381 }
382
383 assert!(
387 alice_central
388 .find_credential_bundle(
389 case.signature_scheme(),
390 case.credential_type,
391 &old_credential.signature_key.public().into()
392 )
393 .await
394 .is_some()
395 );
396
397 let before_delete = alice_central.transaction.count_entities().await;
399 assert_eq!(
400 before_delete.hpke_private_key - before_rotate.hpke_private_key,
401 NB_KEY_PACKAGE
402 );
403
404 assert_eq!(before_delete.key_package - before_rotate.key_package, NB_KEY_PACKAGE);
406
407 assert!(
409 alice_central
410 .find_signature_keypair_from_keystore(old_credential.signature_key.public())
411 .await
412 .is_some()
413 );
414
415 alice_central
418 .transaction
419 .delete_stale_key_packages(case.ciphersuite())
420 .await
421 .unwrap();
422
423 let nb_x509_kp = alice_central
425 .count_key_package(case.ciphersuite(), Some(MlsCredentialType::X509))
426 .await;
427 assert_eq!(nb_x509_kp, NB_KEY_PACKAGE);
428 let nb_basic_kp = alice_central
430 .count_key_package(case.ciphersuite(), Some(MlsCredentialType::Basic))
431 .await;
432 assert_eq!(nb_basic_kp, 0);
433
434 let after_delete = alice_central.transaction.count_entities().await;
438 assert_eq!(after_delete.credential, 1);
439 assert!(
440 alice_central
441 .find_credential_from_keystore(&old_credential)
442 .await
443 .is_none()
444 );
445
446 assert_eq!(after_delete.hpke_private_key, NB_KEY_PACKAGE);
448
449 assert_eq!(
451 after_rotate.encryption_keypair - after_delete.encryption_keypair,
452 INITIAL_KEYING_MATERIAL_COUNT
453 );
454
455 let id = conversation_id();
457 charlie_central
458 .transaction
459 .new_conversation(&id, case.credential_type, case.cfg.clone())
460 .await
461 .unwrap();
462 let alice = alice_central
464 .rand_key_package_of_type(&case, MlsCredentialType::X509)
465 .await;
466 charlie_central
467 .invite_all_members(&case, &id, [(&alice_central, alice)])
468 .await
469 .unwrap();
470 })
471 .await
472 }
473
474 #[apply(all_cred_cipher)]
475 #[wasm_bindgen_test]
476 async fn should_restore_credentials_in_order(case: TestContext) {
477 let [mut alice_central] = case.sessions().await;
478 Box::pin(async move {
479 let x509_test_chain_arc =
480 e2ei_utils::failsafe_ctx(&mut [&mut alice_central], case.signature_scheme()).await;
481
482 let x509_test_chain = x509_test_chain_arc.as_ref().as_ref().unwrap();
483
484 let id = conversation_id();
485 alice_central
486 .transaction
487 .new_conversation(&id, case.credential_type, case.cfg.clone())
488 .await
489 .unwrap();
490
491 let old_cb = alice_central
492 .find_most_recent_credential_bundle(case.signature_scheme(), case.credential_type)
493 .await
494 .unwrap()
495 .clone();
496
497 async_std::task::sleep(core::time::Duration::from_secs(1)).await;
500
501 let is_renewal = case.credential_type == MlsCredentialType::X509;
502
503 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
504 &mut alice_central,
505 &case,
506 x509_test_chain,
507 None,
508 is_renewal,
509 e2ei_utils::init_activation_or_rotation,
510 e2ei_utils::noop_restore,
511 )
512 .await
513 .unwrap();
514
515 alice_central
516 .transaction
517 .save_x509_credential(&mut enrollment, cert)
518 .await
519 .unwrap();
520
521 let cb = alice_central
523 .find_most_recent_credential_bundle(case.signature_scheme(), MlsCredentialType::X509)
524 .await
525 .unwrap();
526 let identity = cb
527 .to_mls_credential_with_key()
528 .extract_identity(case.ciphersuite(), None)
529 .unwrap();
530 assert_eq!(
531 identity.x509_identity.as_ref().unwrap().display_name,
532 e2ei_utils::NEW_DISPLAY_NAME
533 );
534 assert_eq!(
535 identity.x509_identity.as_ref().unwrap().handle,
536 format!("wireapp://%40{}@world.com", e2ei_utils::NEW_HANDLE)
537 );
538
539 let old_spk = SignaturePublicKey::from(old_cb.signature_key.public());
541 let old_cb_found = alice_central
542 .find_credential_bundle(case.signature_scheme(), case.credential_type, &old_spk)
543 .await
544 .unwrap();
545 assert_eq!(old_cb, old_cb_found);
546 let (cid, all_credentials, scs, old_nb_identities) = {
547 let alice_client = alice_central.session().await;
548 let old_nb_identities = alice_client.identities_count().await.unwrap();
549
550 let cid = alice_client.id().await.unwrap();
552 let scs = HashSet::from([case.signature_scheme()]);
553 let all_credentials = alice_central
554 .transaction
555 .keystore()
556 .await
557 .unwrap()
558 .find_all::<MlsCredential>(EntityFindParams::default())
559 .await
560 .unwrap()
561 .into_iter()
562 .map(|c| {
563 let credential =
564 openmls::prelude::Credential::tls_deserialize(&mut c.credential.as_slice()).unwrap();
565 (credential, c.created_at)
566 })
567 .collect::<Vec<_>>();
568 assert_eq!(all_credentials.len(), 2);
569 (cid, all_credentials, scs, old_nb_identities)
570 };
571 let backend = &alice_central.transaction.mls_provider().await.unwrap();
572 backend.keystore().commit_transaction().await.unwrap();
573 backend.keystore().new_transaction().await.unwrap();
574
575 let new_client = alice_central.session.clone();
576 new_client.reset().await;
577
578 new_client.load(backend, &cid, all_credentials, scs).await.unwrap();
579
580 let cb = new_client
582 .find_most_recent_credential_bundle(case.signature_scheme(), MlsCredentialType::X509)
583 .await
584 .unwrap();
585 let identity = cb
586 .to_mls_credential_with_key()
587 .extract_identity(case.ciphersuite(), None)
588 .unwrap();
589
590 assert_eq!(
591 identity.x509_identity.as_ref().unwrap().display_name,
592 e2ei_utils::NEW_DISPLAY_NAME
593 );
594 assert_eq!(
595 identity.x509_identity.as_ref().unwrap().handle,
596 format!("wireapp://%40{}@world.com", e2ei_utils::NEW_HANDLE)
597 );
598
599 assert_eq!(new_client.identities_count().await.unwrap(), old_nb_identities);
600 })
601 .await
602 }
603
604 #[apply(all_cred_cipher)]
605 #[wasm_bindgen_test]
606 async fn rotate_should_roundtrip(case: TestContext) {
607 let [mut alice_central, mut bob_central] = case.sessions().await;
608 Box::pin(async move {
609 let x509_test_chain_arc =
610 e2ei_utils::failsafe_ctx(&mut [&mut alice_central, &mut bob_central], case.signature_scheme())
611 .await;
612
613 let x509_test_chain = x509_test_chain_arc.as_ref().as_ref().unwrap();
614
615 let id = conversation_id();
616 alice_central
617 .transaction
618 .new_conversation(&id, case.credential_type, case.cfg.clone())
619 .await
620 .unwrap();
621
622 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
623 const ALICE_NEW_HANDLE: &str = "new_alice_wire";
625 const ALICE_NEW_DISPLAY_NAME: &str = "New Alice Smith";
626
627 fn init_alice(wrapper: e2ei_utils::E2eiInitWrapper) -> e2ei_utils::InitFnReturn<'_> {
628 Box::pin(async move {
629 let e2ei_utils::E2eiInitWrapper { context: cc, case } = wrapper;
630 let cs = case.ciphersuite();
631 match case.credential_type {
632 MlsCredentialType::Basic => {
633 cc.e2ei_new_activation_enrollment(
634 ALICE_NEW_DISPLAY_NAME.to_string(),
635 ALICE_NEW_HANDLE.to_string(),
636 Some(TEAM.to_string()),
637 e2ei_utils::E2EI_EXPIRY,
638 cs,
639 )
640 .await
641 }
642 MlsCredentialType::X509 => {
643 cc.e2ei_new_rotate_enrollment(
644 Some(ALICE_NEW_DISPLAY_NAME.to_string()),
645 Some(ALICE_NEW_HANDLE.to_string()),
646 Some(TEAM.to_string()),
647 E2EI_EXPIRY,
648 cs,
649 )
650 .await
651 }
652 }
653 .map_err(RecursiveError::transaction("creating new enrollment"))
654 .map_err(Into::into)
655 })
656 }
657
658 let is_renewal = case.credential_type == MlsCredentialType::X509;
659
660 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
661 &mut alice_central,
662 &case,
663 x509_test_chain,
664 None,
665 is_renewal,
666 init_alice,
667 e2ei_utils::noop_restore,
668 )
669 .await
670 .unwrap();
671
672 alice_central
673 .transaction
674 .save_x509_credential(&mut enrollment, cert)
675 .await
676 .unwrap();
677 alice_central
678 .transaction
679 .conversation(&id)
680 .await
681 .unwrap()
682 .e2ei_rotate(None)
683 .await
684 .unwrap();
685
686 let commit = alice_central.mls_transport().await.latest_commit().await;
687
688 let decrypted = bob_central
689 .transaction
690 .conversation(&id)
691 .await
692 .unwrap()
693 .decrypt_message(commit.to_bytes().unwrap())
694 .await
695 .unwrap();
696 alice_central.verify_sender_identity(&case, &decrypted).await;
697
698 alice_central
699 .verify_local_credential_rotated(&id, ALICE_NEW_HANDLE, ALICE_NEW_DISPLAY_NAME)
700 .await;
701
702 const BOB_NEW_HANDLE: &str = "new_bob_wire";
704 const BOB_NEW_DISPLAY_NAME: &str = "New Bob Smith";
705
706 fn init_bob(wrapper: e2ei_utils::E2eiInitWrapper) -> e2ei_utils::InitFnReturn<'_> {
707 Box::pin(async move {
708 let e2ei_utils::E2eiInitWrapper { context: cc, case } = wrapper;
709 let cs = case.ciphersuite();
710 match case.credential_type {
711 MlsCredentialType::Basic => {
712 cc.e2ei_new_activation_enrollment(
713 BOB_NEW_DISPLAY_NAME.to_string(),
714 BOB_NEW_HANDLE.to_string(),
715 Some(TEAM.to_string()),
716 E2EI_EXPIRY,
717 cs,
718 )
719 .await
720 }
721 MlsCredentialType::X509 => {
722 cc.e2ei_new_rotate_enrollment(
723 Some(BOB_NEW_DISPLAY_NAME.to_string()),
724 Some(BOB_NEW_HANDLE.to_string()),
725 Some(TEAM.to_string()),
726 E2EI_EXPIRY,
727 cs,
728 )
729 .await
730 }
731 }
732 .map_err(RecursiveError::transaction("creating new enrollment"))
733 .map_err(Into::into)
734 })
735 }
736 let is_renewal = case.credential_type == MlsCredentialType::X509;
737
738 let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
739 &mut bob_central,
740 &case,
741 x509_test_chain,
742 None,
743 is_renewal,
744 init_bob,
745 e2ei_utils::noop_restore,
746 )
747 .await
748 .unwrap();
749
750 bob_central
751 .transaction
752 .save_x509_credential(&mut enrollment, cert)
753 .await
754 .unwrap();
755
756 bob_central
757 .transaction
758 .conversation(&id)
759 .await
760 .unwrap()
761 .e2ei_rotate(None)
762 .await
763 .unwrap();
764
765 let commit = bob_central.mls_transport().await.latest_commit().await;
766
767 let decrypted = alice_central
768 .transaction
769 .conversation(&id)
770 .await
771 .unwrap()
772 .decrypt_message(commit.to_bytes().unwrap())
773 .await
774 .unwrap();
775 bob_central.verify_sender_identity(&case, &decrypted).await;
776
777 bob_central
778 .verify_local_credential_rotated(&id, BOB_NEW_HANDLE, BOB_NEW_DISPLAY_NAME)
779 .await;
780 })
781 .await
782 }
783 }
784
785 mod one {
786 use super::*;
787 use crate::mls::conversation::Conversation as _;
788
789 #[apply(all_cred_cipher)]
790 #[wasm_bindgen_test]
791 pub async fn should_rotate_one_conversations_credential(case: TestContext) {
792 if case.is_x509() {
793 let [alice_central, bob_central] = case.sessions().await;
794 Box::pin(async move {
795 let id = conversation_id();
796 alice_central
797 .transaction
798 .new_conversation(&id, case.credential_type, case.cfg.clone())
799 .await
800 .unwrap();
801
802 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
803
804 let init_count = alice_central.transaction.count_entities().await;
805 let x509_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
806
807 let intermediate_ca = x509_test_chain.find_local_intermediate_ca();
808
809 let alice_cid = alice_central.get_client_id().await;
811 let (new_handle, new_display_name) = ("new_alice_wire", "New Alice Smith");
812 let cb = alice_central
813 .save_new_credential(&case, new_handle, new_display_name, intermediate_ca)
814 .await;
815
816 let alice_old_identities = alice_central
818 .transaction
819 .conversation(&id)
820 .await
821 .unwrap()
822 .get_device_identities(&[alice_cid])
823 .await
824 .unwrap();
825 let alice_old_identity = alice_old_identities.first().unwrap();
826 assert_ne!(
827 alice_old_identity.x509_identity.as_ref().unwrap().display_name,
828 new_display_name
829 );
830 assert_ne!(
831 alice_old_identity.x509_identity.as_ref().unwrap().handle,
832 format!("{new_handle}@world.com")
833 );
834
835 alice_central
837 .transaction
838 .conversation(&id)
839 .await
840 .unwrap()
841 .e2ei_rotate(Some(&cb))
842 .await
843 .unwrap();
844 let commit = alice_central.mls_transport().await.latest_commit().await;
845
846 let decrypted = bob_central
848 .transaction
849 .conversation(&id)
850 .await
851 .unwrap()
852 .decrypt_message(commit.to_bytes().unwrap())
853 .await
854 .unwrap();
855 alice_central.verify_sender_identity(&case, &decrypted).await;
857
858 alice_central
860 .verify_local_credential_rotated(&id, new_handle, new_display_name)
861 .await;
862
863 let final_count = alice_central.transaction.count_entities().await;
864 assert_eq!(init_count.encryption_keypair, final_count.encryption_keypair);
865 assert_eq!(
866 init_count.epoch_encryption_keypair,
867 final_count.epoch_encryption_keypair
868 );
869 assert_eq!(init_count.key_package, final_count.key_package);
870 })
871 .await
872 }
873 }
874
875 #[apply(all_cred_cipher)]
876 #[wasm_bindgen_test]
877 pub async fn rotate_should_be_renewable_when_commit_denied(case: TestContext) {
878 if !case.is_x509() {
879 return;
880 }
881
882 let [alice_central, bob_central] = case.sessions().await;
883 Box::pin(async move {
884 let id = conversation_id();
885 alice_central
886 .transaction
887 .new_conversation(&id, case.credential_type, case.cfg.clone())
888 .await
889 .unwrap();
890
891 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
892
893 let init_count = alice_central.transaction.count_entities().await;
894
895 let x509_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
896
897 let intermediate_ca = x509_test_chain.find_local_intermediate_ca();
898
899 let (new_handle, new_display_name) = ("new_alice_wire", "New Alice Smith");
904 let cb = alice_central
905 .save_new_credential(&case, new_handle, new_display_name, intermediate_ca)
906 .await;
907
908 let _rotate_commit = alice_central.create_unmerged_e2ei_rotate_commit(&id, &cb).await;
910
911 bob_central
913 .transaction
914 .conversation(&id)
915 .await
916 .unwrap()
917 .update_key_material()
918 .await
919 .unwrap();
920 let bob_commit = bob_central.mls_transport().await.latest_commit().await;
922
923 let decrypted = alice_central
925 .transaction
926 .conversation(&id)
927 .await
928 .unwrap()
929 .decrypt_message(bob_commit.to_bytes().unwrap())
930 .await
931 .unwrap();
932
933 assert_eq!(decrypted.proposals.len(), 1);
935 let renewed_proposal = decrypted.proposals.first().unwrap();
936 bob_central
937 .transaction
938 .conversation(&id)
939 .await
940 .unwrap()
941 .decrypt_message(renewed_proposal.proposal.to_bytes().unwrap())
942 .await
943 .unwrap();
944
945 alice_central
946 .transaction
947 .conversation(&id)
948 .await
949 .unwrap()
950 .commit_pending_proposals()
951 .await
952 .unwrap();
953
954 alice_central
956 .verify_local_credential_rotated(&id, new_handle, new_display_name)
957 .await;
958
959 let rotate_commit = alice_central.mls_transport().await.latest_commit().await;
960 let decrypted = bob_central
962 .transaction
963 .conversation(&id)
964 .await
965 .unwrap()
966 .decrypt_message(rotate_commit.to_bytes().unwrap())
967 .await
968 .unwrap();
969 alice_central.verify_sender_identity(&case, &decrypted).await;
970
971 let final_count = alice_central.transaction.count_entities().await;
972 assert_eq!(init_count.encryption_keypair, final_count.encryption_keypair);
973 })
979 .await
980 }
981
982 #[apply(all_cred_cipher)]
983 #[wasm_bindgen_test]
984 pub async fn rotate_should_replace_existing_basic_credentials(case: TestContext) {
985 if case.is_x509() {
986 let [alice_central, bob_central] = case.sessions().await;
987 Box::pin(async move {
988 let id = conversation_id();
989 alice_central
990 .transaction
991 .new_conversation(&id, MlsCredentialType::Basic, case.cfg.clone())
992 .await
993 .unwrap();
994
995 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
996
997 let x509_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
998 let intermediate_ca = x509_test_chain.find_local_intermediate_ca();
999
1000 let alice_cid = alice_central.get_client_id().await;
1002 let (new_handle, new_display_name) = ("new_alice_wire", "New Alice Smith");
1003 alice_central
1004 .save_new_credential(&case, new_handle, new_display_name, intermediate_ca)
1005 .await;
1006
1007 let alice_old_identities = alice_central
1009 .transaction
1010 .conversation(&id)
1011 .await
1012 .unwrap()
1013 .get_device_identities(&[alice_cid])
1014 .await
1015 .unwrap();
1016 let alice_old_identity = alice_old_identities.first().unwrap();
1017 assert_eq!(alice_old_identity.credential_type, MlsCredentialType::Basic);
1018 assert_eq!(alice_old_identity.x509_identity, None);
1019
1020 alice_central
1022 .transaction
1023 .conversation(&id)
1024 .await
1025 .unwrap()
1026 .e2ei_rotate(None)
1027 .await
1028 .unwrap();
1029 let commit = alice_central.mls_transport().await.latest_commit().await;
1030
1031 let decrypted = bob_central
1033 .transaction
1034 .conversation(&id)
1035 .await
1036 .unwrap()
1037 .decrypt_message(commit.to_bytes().unwrap())
1038 .await
1039 .unwrap();
1040 alice_central.verify_sender_identity(&case, &decrypted).await;
1042
1043 alice_central
1045 .verify_local_credential_rotated(&id, new_handle, new_display_name)
1046 .await;
1047 })
1048 .await
1049 }
1050 }
1051 }
1052}