1#![doc = include_str!("../docs/encryption.md")]
17#![cfg_attr(target_family = "wasm", allow(unused_imports))]
18
19#[cfg(feature = "experimental-send-custom-to-device")]
20use std::{collections::BTreeSet, ops::Deref};
21use std::{
22 collections::{BTreeMap, HashSet},
23 io::{Cursor, Read, Write},
24 iter,
25 path::{Path, PathBuf},
26 str::FromStr,
27 sync::Arc,
28 time::Duration,
29};
30
31#[cfg(feature = "experimental-send-custom-to-device")]
32use as_variant::as_variant;
33use eyeball::{SharedObservable, Subscriber};
34use futures_core::Stream;
35use futures_util::{
36 future::try_join,
37 stream::{self, StreamExt},
38};
39#[cfg(feature = "experimental-send-custom-to-device")]
40use matrix_sdk_base::crypto::CollectStrategy;
41use matrix_sdk_base::{
42 StateStoreDataKey, StateStoreDataValue,
43 cross_process_lock::{AcquireCrossProcessLockFn, CrossProcessLock, CrossProcessLockError},
44 crypto::{
45 CrossSigningBootstrapRequests, OlmMachine,
46 store::{
47 LockableCryptoStore, SecretImportError,
48 types::{RoomKeyBundleInfo, RoomKeyInfo},
49 },
50 types::{
51 SecretsBundle, SignedKey,
52 requests::{
53 OutgoingRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
54 },
55 },
56 },
57 sleep::sleep,
58 timeout::timeout,
59};
60use matrix_sdk_common::{executor::spawn, locks::Mutex as StdMutex};
61use ruma::{
62 DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, TransactionId, UserId,
63 api::{
64 client::{
65 keys::{
66 get_keys, upload_keys, upload_signatures::v3::Request as UploadSignaturesRequest,
67 upload_signing_keys::v3::Request as UploadSigningKeysRequest,
68 },
69 message::send_message_event,
70 to_device::send_event_to_device::v3::{
71 Request as RumaToDeviceRequest, Response as ToDeviceResponse,
72 },
73 uiaa::{AuthData, AuthType, OAuthParams, UiaaInfo},
74 },
75 error::{ErrorBody, StandardErrorBody},
76 },
77 assign,
78 events::room::{
79 MediaSource, ThumbnailInfo,
80 member::{MembershipChange, OriginalSyncRoomMemberEvent},
81 },
82};
83#[cfg(feature = "experimental-send-custom-to-device")]
84use ruma::{
85 events::{AnyToDeviceEventContent, ToDeviceEventType},
86 serde::Raw,
87 to_device::DeviceIdOrAllDevices,
88};
89use serde::{Deserialize, de::Error as _};
90use tasks::BundleReceiverTask;
91use tokio::sync::{Mutex, RwLockReadGuard};
92use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
93use tracing::{Instrument, Span, debug, error, instrument, warn};
94use url::Url;
95use vodozemac::Curve25519PublicKey;
96
97use self::{
98 backups::{Backups, types::BackupClientState},
99 futures::UploadEncryptedFile,
100 identities::{Device, DeviceUpdates, IdentityUpdates, UserDevices, UserIdentity},
101 recovery::{Recovery, RecoveryState},
102 secret_storage::SecretStorage,
103 tasks::{BackupDownloadTask, BackupUploadingTask, ClientTasks},
104 verification::{SasVerification, Verification, VerificationRequest},
105};
106use crate::{
107 Client, Error, HttpError, Result, Room, TransmissionProgress,
108 attachment::Thumbnail,
109 client::{ClientInner, WeakClient},
110 cross_process_lock::CrossProcessLockGuard,
111 error::HttpResult,
112};
113
114pub mod backups;
115pub mod dehydrated_devices;
116pub mod futures;
117pub mod identities;
118pub mod recovery;
119pub mod secret_storage;
120pub(crate) mod tasks;
121pub mod verification;
122
123use matrix_sdk_base::crypto::OlmMachineBuilder;
124pub use matrix_sdk_base::crypto::{
125 CrossSigningStatus, CryptoStoreError, DecryptorError, EventError, KeyExportError, LocalTrust,
126 MediaEncryptionInfo, MegolmError, OlmError, RoomKeyImportResult, SessionCreationError,
127 SignatureError, VERSION,
128 olm::{
129 SessionCreationError as MegolmSessionCreationError,
130 SessionExportError as OlmSessionExportError,
131 },
132 vodozemac,
133};
134use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
135
136#[cfg(feature = "experimental-send-custom-to-device")]
137use crate::config::RequestConfig;
138pub use crate::error::RoomKeyImportError;
139
140#[cfg(feature = "sqlite")]
143#[derive(Debug, thiserror::Error)]
144pub enum BundleExportError {
145 #[error(transparent)]
147 OpenStoreError(#[from] matrix_sdk_sqlite::OpenStoreError),
148 #[error(transparent)]
150 StoreError(#[from] CryptoStoreError),
151 #[error(transparent)]
154 SecretExport(#[from] matrix_sdk_base::crypto::store::SecretsBundleExportError),
155}
156
157#[derive(Debug, thiserror::Error)]
160pub enum BundleImportError {
161 #[error(transparent)]
163 SecretImport(#[from] SecretImportError),
164 #[error(transparent)]
166 DeviceKeys(#[from] Error),
167}
168
169#[cfg(feature = "sqlite")]
178pub async fn export_secrets_bundle_from_store(
179 database_path: impl AsRef<Path>,
180 passphrase: Option<&str>,
181) -> std::result::Result<Option<(OwnedUserId, SecretsBundle)>, BundleExportError> {
182 use matrix_sdk_base::crypto::store::CryptoStore;
183
184 let store = matrix_sdk_sqlite::SqliteCryptoStore::open(database_path, passphrase).await?;
185 let account =
186 store.load_account().await.map_err(|e| BundleExportError::StoreError(e.into()))?;
187
188 if let Some(account) = account {
189 let machine = OlmMachineBuilder::new(&account.user_id, &account.device_id)
190 .with_crypto_store(store)
191 .build()
192 .await
193 .map_err(BundleExportError::StoreError)?;
194
195 let bundle = machine.store().export_secrets_bundle().await?;
196
197 Ok(Some((account.user_id.to_owned(), bundle)))
198 } else {
199 Ok(None)
200 }
201}
202
203pub(crate) struct EncryptionData {
205 pub tasks: StdMutex<ClientTasks>,
208
209 pub encryption_settings: EncryptionSettings,
211
212 pub backup_state: BackupClientState,
214
215 pub recovery_state: SharedObservable<RecoveryState>,
217
218 pub dehydrated_devices_state: dehydrated_devices::DehydratedDevicesState,
221}
222
223impl EncryptionData {
224 pub fn new(encryption_settings: EncryptionSettings) -> Self {
225 Self {
226 encryption_settings,
227
228 tasks: StdMutex::new(Default::default()),
229 backup_state: Default::default(),
230 recovery_state: Default::default(),
231 dehydrated_devices_state: Default::default(),
232 }
233 }
234
235 pub fn initialize_tasks(&self, client: &Arc<ClientInner>) {
236 let weak_client = WeakClient::from_inner(client);
237
238 let mut tasks = self.tasks.lock();
239 tasks.upload_room_keys = Some(BackupUploadingTask::new(weak_client.clone()));
240
241 if self.encryption_settings.backup_download_strategy
242 == BackupDownloadStrategy::AfterDecryptionFailure
243 {
244 tasks.download_room_keys = Some(BackupDownloadTask::new(weak_client));
245 }
246 }
247
248 pub fn initialize_recovery_state_update_task(&self, client: &Client) {
254 let mut guard = self.tasks.lock();
255
256 let future = Recovery::update_state_after_backup_state_change(client);
257 let join_handle = spawn(future);
258
259 guard.update_recovery_state_after_backup = Some(join_handle);
260 }
261}
262
263#[derive(Clone, Copy, Debug, Default)]
265pub struct EncryptionSettings {
266 pub auto_enable_cross_signing: bool,
272
273 pub backup_download_strategy: BackupDownloadStrategy,
278
279 pub auto_enable_backups: bool,
281}
282
283#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
285#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
286pub enum BackupDownloadStrategy {
287 OneShot,
298
299 AfterDecryptionFailure,
301
302 #[default]
307 Manual,
308}
309
310#[derive(Clone, Copy, Debug, Eq, PartialEq)]
315pub enum VerificationState {
316 Unknown,
318 Verified,
321 Unverified,
323}
324
325#[derive(Debug)]
336pub struct CrossSigningResetHandle {
337 client: Client,
338 upload_request: UploadSigningKeysRequest,
339 signatures_request: UploadSignaturesRequest,
340 auth_type: CrossSigningResetAuthType,
341 is_cancelled: Mutex<bool>,
342}
343
344impl CrossSigningResetHandle {
345 pub fn new(
347 client: Client,
348 upload_request: UploadSigningKeysRequest,
349 signatures_request: UploadSignaturesRequest,
350 auth_type: CrossSigningResetAuthType,
351 ) -> Self {
352 Self {
353 client,
354 upload_request,
355 signatures_request,
356 auth_type,
357 is_cancelled: Mutex::new(false),
358 }
359 }
360
361 pub fn auth_type(&self) -> &CrossSigningResetAuthType {
364 &self.auth_type
365 }
366
367 pub async fn auth(&self, auth: Option<AuthData>) -> Result<()> {
371 const RETRY_EVERY: Duration = Duration::from_millis(500);
373
374 const TIMEOUT: Duration = Duration::from_mins(2);
376
377 timeout(
378 async {
379 let mut upload_request = self.upload_request.clone();
380 upload_request.auth = auth;
381
382 debug!(
383 "Repeatedly PUTting to keys/device_signing/upload until it works \
384 or we hit a permanent failure."
385 );
386 while let Err(e) = self.client.send(upload_request.clone()).await {
387 if *self.is_cancelled.lock().await {
388 return Ok(());
389 }
390
391 match e.as_uiaa_response() {
392 Some(uiaa_info) => {
393 if !matches!(self.auth_type, CrossSigningResetAuthType::OAuth(_))
396 && uiaa_info.auth_error.is_some()
397 {
398 return Err(e.into());
399 }
400 }
401 None => return Err(e.into()),
402 }
403
404 debug!(
405 "PUT to keys/device_signing/upload failed with 401. Retrying after \
406 a short delay."
407 );
408 sleep(RETRY_EVERY).await;
409 }
410
411 self.client.send(self.signatures_request.clone()).await?;
412
413 Ok(())
414 },
415 TIMEOUT,
416 )
417 .await
418 .unwrap_or_else(|_| {
419 warn!("Timed out waiting for keys/device_signing/upload to succeed.");
420 Err(Error::Timeout)
421 })
422 }
423
424 pub async fn cancel(&self) {
426 *self.is_cancelled.lock().await = true;
427 }
428}
429
430#[derive(Debug, Clone)]
433pub enum CrossSigningResetAuthType {
434 Uiaa(UiaaInfo),
436 OAuth(OAuthCrossSigningResetInfo),
439}
440
441impl CrossSigningResetAuthType {
442 fn new(error: &HttpError) -> Result<Option<Self>> {
443 if let Some(auth_info) = error.as_uiaa_response() {
444 if let Ok(Some(auth_info)) = OAuthCrossSigningResetInfo::from_auth_info(auth_info) {
445 Ok(Some(CrossSigningResetAuthType::OAuth(auth_info)))
446 } else {
447 Ok(Some(CrossSigningResetAuthType::Uiaa(auth_info.clone())))
448 }
449 } else {
450 Ok(None)
451 }
452 }
453}
454
455#[derive(Debug, Clone, Deserialize)]
458pub struct OAuthCrossSigningResetInfo {
459 pub approval_url: Url,
461
462 pub session: Option<String>,
464}
465
466impl OAuthCrossSigningResetInfo {
467 fn from_auth_info(auth_info: &UiaaInfo) -> Result<Option<Self>> {
468 let Some(parameters) = auth_info.params::<OAuthParams>(&AuthType::OAuth)? else {
469 return Ok(None);
470 };
471
472 Ok(Some(OAuthCrossSigningResetInfo {
473 approval_url: parameters.url.as_str().try_into()?,
474 session: auth_info.session.clone(),
475 }))
476 }
477}
478
479#[derive(Clone, Debug)]
482pub struct DuplicateOneTimeKeyErrorMessage {
483 pub old_key: Curve25519PublicKey,
485 pub new_key: Curve25519PublicKey,
487}
488
489impl FromStr for DuplicateOneTimeKeyErrorMessage {
490 type Err = serde_json::Error;
491
492 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
493 let mut split = s.split_terminator(';');
497
498 let old_key = split
499 .next()
500 .ok_or(serde_json::Error::custom("Old key is missing in the error message"))?;
501 let new_key = split
502 .next()
503 .ok_or(serde_json::Error::custom("New key is missing in the error message"))?;
504
505 let old_key_index = old_key
508 .find("Old key:")
509 .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
510
511 let old_key = old_key[old_key_index..]
512 .trim()
513 .strip_prefix("Old key:")
514 .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
515
516 let new_key = new_key
519 .trim()
520 .strip_prefix("new key:")
521 .ok_or(serde_json::Error::custom("New key is missing the prefix"))?;
522
523 let new_key = new_key.replace("'", "\"");
526
527 let old_key: SignedKey = serde_json::from_str(old_key)?;
529 let new_key: SignedKey = serde_json::from_str(&new_key)?;
530
531 let old_key = old_key.key();
533 let new_key = new_key.key();
534
535 Ok(Self { old_key, new_key })
536 }
537}
538
539impl Client {
540 pub(crate) async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
541 self.base_client().olm_machine().await
542 }
543
544 pub(crate) async fn mark_request_as_sent(
545 &self,
546 request_id: &TransactionId,
547 response: impl Into<matrix_sdk_base::crypto::types::requests::AnyIncomingResponse<'_>>,
548 ) -> Result<(), matrix_sdk_base::Error> {
549 Ok(self
550 .olm_machine()
551 .await
552 .as_ref()
553 .expect(
554 "We should have an olm machine once we try to mark E2EE related requests as sent",
555 )
556 .mark_request_as_sent(request_id, response)
557 .await?)
558 }
559
560 #[instrument(skip(self, device_keys))]
566 pub(crate) async fn keys_query(
567 &self,
568 request_id: &TransactionId,
569 device_keys: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
570 ) -> Result<get_keys::v3::Response> {
571 let request = assign!(get_keys::v3::Request::new(), { device_keys });
572
573 let response = self.send(request).await?;
574 self.mark_request_as_sent(request_id, &response).await?;
575 self.encryption().update_state_after_keys_query(&response).await;
576
577 Ok(response)
578 }
579
580 pub fn upload_encrypted_file<'a, R: Read + ?Sized + 'a>(
614 &'a self,
615 reader: &'a mut R,
616 ) -> UploadEncryptedFile<'a, R> {
617 UploadEncryptedFile::new(self, reader)
618 }
619
620 pub(crate) async fn upload_encrypted_media_and_thumbnail(
623 &self,
624 data: &[u8],
625 thumbnail: Option<Thumbnail>,
626 send_progress: SharedObservable<TransmissionProgress>,
627 ) -> Result<(MediaSource, Option<(MediaSource, Box<ThumbnailInfo>)>)> {
628 let upload_thumbnail = self.upload_encrypted_thumbnail(thumbnail, send_progress.clone());
629
630 let upload_attachment = async {
631 let mut cursor = Cursor::new(data);
632 self.upload_encrypted_file(&mut cursor)
633 .with_send_progress_observable(send_progress)
634 .await
635 };
636
637 let (thumbnail, file) = try_join(upload_thumbnail, upload_attachment).await?;
638
639 Ok((MediaSource::Encrypted(Box::new(file)), thumbnail))
640 }
641
642 async fn upload_encrypted_thumbnail(
645 &self,
646 thumbnail: Option<Thumbnail>,
647 send_progress: SharedObservable<TransmissionProgress>,
648 ) -> Result<Option<(MediaSource, Box<ThumbnailInfo>)>> {
649 let Some(thumbnail) = thumbnail else {
650 return Ok(None);
651 };
652
653 let (data, _, thumbnail_info) = thumbnail.into_parts();
654 let mut cursor = Cursor::new(data);
655
656 let file = self
657 .upload_encrypted_file(&mut cursor)
658 .with_send_progress_observable(send_progress)
659 .await?;
660
661 Ok(Some((MediaSource::Encrypted(Box::new(file)), thumbnail_info)))
662 }
663
664 pub(crate) async fn claim_one_time_keys(
670 &self,
671 users: impl Iterator<Item = &UserId>,
672 ) -> Result<()> {
673 let _lock = self.locks().key_claim_lock.lock().await;
674
675 if let Some((request_id, request)) = self
676 .olm_machine()
677 .await
678 .as_ref()
679 .ok_or(Error::NoOlmMachine)?
680 .get_missing_sessions(users)
681 .await?
682 {
683 let response = self.send(request).await?;
684 self.mark_request_as_sent(&request_id, &response).await?;
685 }
686
687 Ok(())
688 }
689
690 #[instrument(skip(self, request))]
700 pub(crate) async fn keys_upload(
701 &self,
702 request_id: &TransactionId,
703 request: &upload_keys::v3::Request,
704 ) -> Result<upload_keys::v3::Response> {
705 debug!(
706 device_keys = request.device_keys.is_some(),
707 one_time_key_count = request.one_time_keys.len(),
708 "Uploading public encryption keys",
709 );
710
711 let response = self.send(request.clone()).await?;
712 self.mark_request_as_sent(request_id, &response).await?;
713
714 Ok(response)
715 }
716
717 pub(crate) async fn room_send_helper(
718 &self,
719 request: &RoomMessageRequest,
720 ) -> Result<send_message_event::v3::Response> {
721 let content = request.content.clone();
722 let txn_id = request.txn_id.clone();
723 let room_id = &request.room_id;
724
725 self.get_room(room_id)
726 .expect("Can't send a message to a room that isn't known to the store")
727 .send(*content)
728 .with_transaction_id(txn_id)
729 .await
730 .map(|result| result.response)
731 }
732
733 pub(crate) async fn send_to_device(
734 &self,
735 request: &ToDeviceRequest,
736 ) -> HttpResult<ToDeviceResponse> {
737 let request = RumaToDeviceRequest::new_raw(
738 request.event_type.clone(),
739 request.txn_id.clone(),
740 request.messages.clone(),
741 );
742
743 self.send(request).await
744 }
745
746 pub(crate) async fn send_verification_request(
747 &self,
748 request: OutgoingVerificationRequest,
749 ) -> Result<()> {
750 use matrix_sdk_base::crypto::types::requests::OutgoingVerificationRequest::*;
751
752 match request {
753 ToDevice(t) => {
754 self.send_to_device(&t).await?;
755 }
756 InRoom(r) => {
757 self.room_send_helper(&r).await?;
758 }
759 }
760
761 Ok(())
762 }
763
764 async fn send_outgoing_request(&self, r: OutgoingRequest) -> Result<()> {
765 use matrix_sdk_base::crypto::types::requests::AnyOutgoingRequest;
766
767 match r.request() {
768 AnyOutgoingRequest::KeysQuery(request) => {
769 self.keys_query(r.request_id(), request.device_keys.clone()).await?;
770 }
771 AnyOutgoingRequest::KeysUpload(request) => {
772 let response = self.keys_upload(r.request_id(), request).await;
773
774 if let Err(e) = &response {
775 match e.as_client_api_error() {
776 Some(e) if e.status_code == 400 => {
777 if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &e.body
778 {
779 {
785 let already_reported = self
786 .state_store()
787 .get_kv_data(StateStoreDataKey::OneTimeKeyAlreadyUploaded)
788 .await?
789 .is_some();
790
791 if message.starts_with("One time key") && !already_reported {
792 let error_message =
793 DuplicateOneTimeKeyErrorMessage::from_str(message);
794
795 if let Ok(message) = &error_message {
796 error!(
797 sentry = true,
798 old_key = %message.old_key,
799 new_key = %message.new_key,
800 "Duplicate one-time keys have been uploaded"
801 );
802 } else {
803 error!(
804 sentry = true,
805 "Duplicate one-time keys have been uploaded"
806 );
807 }
808
809 self.state_store()
810 .set_kv_data(
811 StateStoreDataKey::OneTimeKeyAlreadyUploaded,
812 StateStoreDataValue::OneTimeKeyAlreadyUploaded,
813 )
814 .await?;
815
816 if let Err(e) = self
817 .inner
818 .duplicate_key_upload_error_sender
819 .send(error_message.ok())
820 {
821 error!(
822 "Failed to dispatch duplicate key upload error notification: {}",
823 e
824 );
825 }
826 }
827 }
828 }
829 }
830 _ => {}
831 }
832
833 response?;
834 }
835 }
836 AnyOutgoingRequest::ToDeviceRequest(request) => {
837 let response = self.send_to_device(request).await?;
838 self.mark_request_as_sent(r.request_id(), &response).await?;
839 }
840 AnyOutgoingRequest::SignatureUpload(request) => {
841 let response = self.send(request.clone()).await?;
842 self.mark_request_as_sent(r.request_id(), &response).await?;
843 }
844 AnyOutgoingRequest::RoomMessage(request) => {
845 let response = self.room_send_helper(request).await?;
846 self.mark_request_as_sent(r.request_id(), &response).await?;
847 }
848 AnyOutgoingRequest::KeysClaim(request) => {
849 let response = self.send(request.clone()).await?;
850 self.mark_request_as_sent(r.request_id(), &response).await?;
851 }
852 }
853
854 Ok(())
855 }
856
857 #[instrument(skip_all)]
858 pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
859 const MAX_CONCURRENT_REQUESTS: usize = 20;
860
861 if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
864 warn!("Error while claiming one-time keys {:?}", e);
865 }
866
867 let outgoing_requests = stream::iter(
868 self.olm_machine()
869 .await
870 .as_ref()
871 .ok_or(Error::NoOlmMachine)?
872 .outgoing_requests()
873 .await?,
874 )
875 .map(|r| self.send_outgoing_request(r));
876
877 let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
878
879 requests
880 .for_each(|r| async move {
881 match r {
882 Ok(_) => (),
883 Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
884 }
885 })
886 .await;
887
888 Ok(())
889 }
890}
891
892#[cfg(feature = "experimental-send-custom-to-device")]
893impl Client {
894 pub async fn send_encrypted_to_device(
922 &self,
923 event_type: &ToDeviceEventType,
924 recipients: BTreeMap<OwnedUserId, Vec<DeviceIdOrAllDevices>>,
925 content: Raw<AnyToDeviceEventContent>,
926 ) -> Result<BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>> {
927 let mut failures: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>> = BTreeMap::new();
928 let mut recipient_devices = Vec::<_>::new();
929
930 for (user_id, recipient_device_ids) in recipients {
932 let (devices, unknown_devices) =
933 self.resolve_recipient_devices(&user_id, recipient_device_ids).await?;
934 recipient_devices.extend(devices);
935 if !unknown_devices.is_empty() {
936 failures.insert(user_id, unknown_devices);
937 }
938 }
939
940 if !recipient_devices.is_empty() {
941 let encrypt_and_send_failures = self
942 .encryption()
943 .encrypt_and_send_raw_to_device(
944 recipient_devices.iter().collect(),
945 &event_type.to_string(),
946 content,
947 CollectStrategy::AllDevices,
948 )
949 .await?;
950
951 for (user_id, device_id) in encrypt_and_send_failures {
952 failures.entry(user_id).or_default().push(device_id)
953 }
954 }
955
956 Ok(failures)
957 }
958
959 async fn resolve_recipient_devices(
968 &self,
969 user_id: &UserId,
970 recipient_device_ids: Vec<DeviceIdOrAllDevices>,
971 ) -> Result<(Vec<Device>, Vec<OwnedDeviceId>)> {
972 let user_devices = self.encryption().get_user_devices(user_id).await?;
973
974 if recipient_device_ids.contains(&DeviceIdOrAllDevices::AllDevices) {
975 let devices: Vec<_> = user_devices.devices().collect();
979
980 if devices.is_empty() {
981 warn!(
982 "Recipient list contains `AllDevices` but no devices found for user {user_id}."
983 );
984 }
985 if recipient_device_ids.len() > 1 {
986 warn!(
987 "The recipient_device_ids list for {user_id} contains both `AllDevices` and explicit `DeviceId` entries. Only consider `AllDevices`",
988 );
989 }
990
991 Ok((devices, Vec::new()))
992 } else {
993 let (found_device_ids, devices): (BTreeSet<_>, Vec<_>) = user_devices
996 .devices()
997 .map(|device| (device.device_id().to_owned(), device))
998 .filter(|(device_id, _)| {
999 recipient_device_ids
1000 .contains(&DeviceIdOrAllDevices::DeviceId(device_id.clone()))
1001 })
1002 .unzip();
1003
1004 let requested_device_ids: BTreeSet<_> = recipient_device_ids
1005 .into_iter()
1006 .filter_map(|d| as_variant!(d, DeviceIdOrAllDevices::DeviceId))
1007 .collect();
1008
1009 let missing_devices =
1012 requested_device_ids.difference(&found_device_ids).map(ToOwned::to_owned).collect();
1013
1014 Ok((devices, missing_devices))
1015 }
1016 }
1017}
1018
1019#[cfg(any(feature = "testing", test))]
1020impl Client {
1021 pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
1023 self.olm_machine().await
1024 }
1025
1026 pub fn abort_bundle_receiver_task(&self) {
1028 let tasks = self.inner.e2ee.tasks.lock();
1029 if let Some(task) = tasks.receive_historic_room_key_bundles.as_ref() {
1030 task.abort()
1031 }
1032 }
1033}
1034
1035#[derive(Debug, Clone)]
1039pub struct Encryption {
1040 client: Client,
1042}
1043
1044impl Encryption {
1045 pub(crate) fn new(client: Client) -> Self {
1046 Self { client }
1047 }
1048
1049 pub(crate) fn settings(&self) -> EncryptionSettings {
1051 self.client.inner.e2ee.encryption_settings
1052 }
1053
1054 pub async fn ed25519_key(&self) -> Option<String> {
1057 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
1058 }
1059
1060 pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
1062 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
1063 }
1064
1065 pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
1067 match self.get_own_device().await {
1068 Ok(Some(device)) => device.first_time_seen_ts(),
1069 _ => MilliSecondsSinceUnixEpoch::now(),
1071 }
1072 }
1073
1074 pub async fn import_secrets_bundle(
1087 &self,
1088 bundle: &SecretsBundle,
1089 ) -> Result<(), BundleImportError> {
1090 self.import_secrets_bundle_impl(bundle).await?;
1091
1092 self.ensure_device_keys_upload().await?;
1095 self.wait_for_e2ee_initialization_tasks().await;
1096
1097 if !self.backups().are_enabled().await {
1102 self.backups().maybe_resume_backups().await?;
1103 }
1104
1105 Ok(())
1106 }
1107
1108 pub(crate) async fn import_secrets_bundle_impl(
1109 &self,
1110 bundle: &SecretsBundle,
1111 ) -> Result<(), SecretImportError> {
1112 let olm_machine = self.client.olm_machine().await;
1113 let olm_machine =
1114 olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
1115
1116 olm_machine.store().import_secrets_bundle(bundle).await
1117 }
1118
1119 pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
1124 let olm = self.client.olm_machine().await;
1125 let machine = olm.as_ref()?;
1126 Some(machine.cross_signing_status().await)
1127 }
1128
1129 pub async fn has_devices_to_verify_against(&self) -> Result<bool> {
1135 let olm_machine = self.client.olm_machine().await;
1136 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1137 let user_id = olm_machine.user_id();
1138
1139 self.ensure_initial_key_query().await?;
1140
1141 let devices = self.get_user_devices(user_id).await?;
1142
1143 let ret = devices.devices().any(|device| {
1144 device.is_cross_signed_by_owner()
1145 && device.curve25519_key().is_some()
1146 && !device.is_dehydrated()
1147 });
1148
1149 Ok(ret)
1150 }
1151
1152 pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
1157 if let Some(machine) = self.client.olm_machine().await.as_ref() {
1158 machine.tracked_users().await
1159 } else {
1160 Ok(HashSet::new())
1161 }
1162 }
1163
1164 pub fn verification_state(&self) -> Subscriber<VerificationState> {
1187 self.client.inner.verification_state.subscribe_reset()
1188 }
1189
1190 pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
1192 let olm = self.client.olm_machine().await;
1193 let olm = olm.as_ref()?;
1194 #[allow(clippy::bind_instead_of_map)]
1195 olm.get_verification(user_id, flow_id).and_then(|v| match v {
1196 matrix_sdk_base::crypto::Verification::SasV1(sas) => {
1197 Some(SasVerification { inner: sas, client: self.client.clone() }.into())
1198 }
1199 #[cfg(feature = "qrcode")]
1200 matrix_sdk_base::crypto::Verification::QrV1(qr) => {
1201 Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
1202 }
1203 _ => None,
1204 })
1205 }
1206
1207 pub async fn get_verification_request(
1210 &self,
1211 user_id: &UserId,
1212 flow_id: impl AsRef<str>,
1213 ) -> Option<VerificationRequest> {
1214 let olm = self.client.olm_machine().await;
1215 let olm = olm.as_ref()?;
1216
1217 olm.get_verification_request(user_id, flow_id)
1218 .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
1219 }
1220
1221 pub async fn get_device(
1254 &self,
1255 user_id: &UserId,
1256 device_id: &DeviceId,
1257 ) -> Result<Option<Device>, CryptoStoreError> {
1258 let olm = self.client.olm_machine().await;
1259 let Some(machine) = olm.as_ref() else { return Ok(None) };
1260 let device = machine.get_device(user_id, device_id, None).await?;
1261 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1262 }
1263
1264 pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
1271 let olm = self.client.olm_machine().await;
1272 let Some(machine) = olm.as_ref() else { return Ok(None) };
1273 let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
1274 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1275 }
1276
1277 pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
1303 let devices = self
1304 .client
1305 .olm_machine()
1306 .await
1307 .as_ref()
1308 .ok_or(Error::NoOlmMachine)?
1309 .get_user_devices(user_id, None)
1310 .await?;
1311
1312 Ok(UserDevices { inner: devices, client: self.client.clone() })
1313 }
1314
1315 pub async fn get_user_identity(
1351 &self,
1352 user_id: &UserId,
1353 ) -> Result<Option<UserIdentity>, CryptoStoreError> {
1354 let olm = self.client.olm_machine().await;
1355 let Some(olm) = olm.as_ref() else { return Ok(None) };
1356 let identity = olm.get_identity(user_id, None).await?;
1357
1358 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1359 }
1360
1361 pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
1399 let olm = self.client.olm_machine().await;
1400 let Some(olm) = olm.as_ref() else { return Ok(None) };
1401
1402 let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
1403 self.client.keys_query(&request_id, request.device_keys).await?;
1404
1405 let identity = olm.get_identity(user_id, None).await?;
1406 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1407 }
1408
1409 pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates> + use<>> {
1440 let olm = self.client.olm_machine().await;
1441 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1442 let client = self.client.to_owned();
1443
1444 Ok(olm
1445 .store()
1446 .devices_stream()
1447 .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1448 }
1449
1450 pub async fn user_identities_stream(
1478 &self,
1479 ) -> Result<impl Stream<Item = IdentityUpdates> + use<>> {
1480 let olm = self.client.olm_machine().await;
1481 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1482 let client = self.client.to_owned();
1483
1484 Ok(olm
1485 .store()
1486 .user_identities_stream()
1487 .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1488 }
1489
1490 pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1534 let olm = self.client.olm_machine().await;
1535 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1536
1537 let CrossSigningBootstrapRequests {
1538 upload_signing_keys_req,
1539 upload_keys_req,
1540 upload_signatures_req,
1541 } = olm.bootstrap_cross_signing(false).await?;
1542
1543 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1544 auth: auth_data,
1545 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1546 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1547 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1548 });
1549
1550 if let Some(req) = upload_keys_req {
1551 self.client.send_outgoing_request(req).await?;
1552 }
1553 self.client.send(upload_signing_keys_req).await?;
1554 self.client.send(upload_signatures_req).await?;
1555
1556 Ok(())
1557 }
1558
1559 pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1600 let olm = self.client.olm_machine().await;
1601 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1602
1603 let CrossSigningBootstrapRequests {
1604 upload_keys_req,
1605 upload_signing_keys_req,
1606 upload_signatures_req,
1607 } = olm.bootstrap_cross_signing(true).await?;
1608
1609 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1610 auth: None,
1611 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1612 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1613 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1614 });
1615
1616 if let Some(req) = upload_keys_req {
1617 self.client.send_outgoing_request(req).await?;
1618 }
1619
1620 if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1621 if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1622 let client = self.client.clone();
1623
1624 Ok(Some(CrossSigningResetHandle::new(
1625 client,
1626 upload_signing_keys_req,
1627 upload_signatures_req,
1628 auth_type,
1629 )))
1630 } else {
1631 Err(error.into())
1632 }
1633 } else {
1634 self.client.send(upload_signatures_req).await?;
1635
1636 Ok(None)
1637 }
1638 }
1639
1640 async fn ensure_initial_key_query(&self) -> Result<()> {
1643 let olm_machine = self.client.olm_machine().await;
1644 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1645
1646 let user_id = olm_machine.user_id();
1647
1648 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1649 let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1650 self.client.keys_query(&request_id, request.device_keys).await?;
1651 }
1652
1653 Ok(())
1654 }
1655
1656 pub async fn bootstrap_cross_signing_if_needed(
1705 &self,
1706 auth_data: Option<AuthData>,
1707 ) -> Result<()> {
1708 let olm_machine = self.client.olm_machine().await;
1709 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1710 let user_id = olm_machine.user_id();
1711
1712 self.ensure_initial_key_query().await?;
1713
1714 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1715 self.bootstrap_cross_signing(auth_data).await?;
1716 }
1717
1718 Ok(())
1719 }
1720
1721 #[cfg(not(target_family = "wasm"))]
1772 pub async fn export_room_keys(
1773 &self,
1774 path: PathBuf,
1775 passphrase: &str,
1776 predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1777 ) -> Result<()> {
1778 let olm = self.client.olm_machine().await;
1779 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1780
1781 let keys = olm.store().export_room_keys(predicate).await?;
1782 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1783
1784 let encrypt = move || -> Result<()> {
1785 let export: String =
1786 matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1787 let mut file = std::fs::File::create(path)?;
1788 file.write_all(&export.into_bytes())?;
1789 Ok(())
1790 };
1791
1792 let task = tokio::task::spawn_blocking(encrypt);
1793 task.await.expect("Task join error")
1794 }
1795
1796 #[cfg(not(target_family = "wasm"))]
1833 pub async fn import_room_keys(
1834 &self,
1835 path: PathBuf,
1836 passphrase: &str,
1837 ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1838 let olm = self.client.olm_machine().await;
1839 let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1840 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1841
1842 let decrypt = move || {
1843 let file = std::fs::File::open(path)?;
1844 matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1845 };
1846
1847 let task = tokio::task::spawn_blocking(decrypt);
1848 let import = task.await.expect("Task join error")?;
1849
1850 let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1851
1852 self.backups().maybe_trigger_backup();
1853
1854 Ok(ret)
1855 }
1856
1857 pub async fn room_keys_received_stream(
1888 &self,
1889 ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<>>
1890 {
1891 let olm = self.client.olm_machine().await;
1892 let olm = olm.as_ref()?;
1893
1894 Some(olm.store().room_keys_received_stream())
1895 }
1896
1897 pub async fn historic_room_key_stream(
1929 &self,
1930 ) -> Option<impl Stream<Item = RoomKeyBundleInfo> + use<>> {
1931 let olm = self.client.olm_machine().await;
1932 let olm = olm.as_ref()?;
1933
1934 Some(olm.store().historic_room_key_stream())
1935 }
1936
1937 pub fn secret_storage(&self) -> SecretStorage {
1939 SecretStorage { client: self.client.to_owned() }
1940 }
1941
1942 pub fn backups(&self) -> Backups {
1944 Backups { client: self.client.to_owned() }
1945 }
1946
1947 pub fn recovery(&self) -> Recovery {
1949 Recovery { client: self.client.to_owned() }
1950 }
1951
1952 pub fn dehydrated_devices(&self) -> dehydrated_devices::DehydratedDevices {
1959 dehydrated_devices::DehydratedDevices { client: self.client.to_owned() }
1960 }
1961
1962 pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
1973 if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1975 let prev_holder = prev_lock.lock_holder();
1976 if prev_holder.is_some() && prev_holder.unwrap() == lock_value {
1977 return Ok(());
1978 }
1979 warn!(
1980 "Recreating cross-process store lock with a different holder value: \
1981 prev was {prev_holder:?}, new is {lock_value}"
1982 );
1983 }
1984
1985 let olm_machine = self.client.base_client().olm_machine().await;
1986 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1987
1988 let lock = olm_machine.store().create_store_lock(
1989 "cross_process_lock".to_owned(),
1990 CrossProcessLockConfig::multi_process(lock_value.to_owned()),
1991 );
1992
1993 {
1999 let lock_result = lock.try_lock_once().await?;
2000
2001 if lock_result.is_ok() {
2002 olm_machine
2003 .initialize_crypto_store_generation(
2004 &self.client.locks().crypto_store_generation,
2005 )
2006 .await?;
2007 }
2008 }
2009
2010 self.client
2011 .locks()
2012 .cross_process_crypto_store_lock
2013 .set(lock)
2014 .map_err(|_| Error::BadCryptoStoreState)?;
2015
2016 Ok(())
2017 }
2018
2019 #[instrument(skip(self), fields(olm_machine_new_generation, olm_machine_generation))]
2024 async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
2025 let olm_machine_guard = self.client.olm_machine().await;
2026 if let Some(olm_machine) = olm_machine_guard.as_ref() {
2027 let (new_gen, generation_number) = olm_machine
2028 .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
2029 .await?;
2030
2031 Span::current()
2032 .record("olm_machine_new_generation", new_gen)
2033 .record("olm_machine_generation", generation_number);
2034 debug!("OlmMachine generation maintained in CryptoStore");
2035
2036 if new_gen {
2038 drop(olm_machine_guard);
2040 self.client.base_client().regenerate_olm(None).await?;
2042 }
2043 Ok(generation_number)
2044 } else {
2045 warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
2050 Ok(0)
2051 }
2052 }
2053
2054 pub async fn spin_lock_store(
2062 &self,
2063 max_backoff: Option<u32>,
2064 ) -> Result<Option<CrossProcessLockGuard>, Error> {
2065 self.lock_store(async move |lock| lock.spin_lock(max_backoff).await).await
2066 }
2067
2068 pub async fn try_lock_store_once(&self) -> Result<Option<CrossProcessLockGuard>, Error> {
2076 match self.lock_store(CrossProcessLock::try_lock_once).await {
2077 Err(Error::CrossProcessLockError(e))
2078 if matches!(*e, CrossProcessLockError::Unobtained(_)) =>
2079 {
2080 Ok(None)
2081 }
2082 other => other,
2083 }
2084 }
2085
2086 pub async fn lock_store<F: AcquireCrossProcessLockFn<LockableCryptoStore>>(
2093 &self,
2094 acquire: F,
2095 ) -> Result<Option<CrossProcessLockGuard>, Error> {
2096 let wrap_err = |e: CryptoStoreError| {
2097 Error::CrossProcessLockError(Box::new(CrossProcessLockError::TryLock(Arc::new(e))))
2098 };
2099 if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
2100 let guard = acquire(lock).await.map_err(wrap_err)??;
2101 let _ = self.on_lock_newly_acquired().await?;
2102 Ok(Some(guard.into_guard()))
2103 } else {
2104 Ok(None)
2105 }
2106 }
2107
2108 #[cfg(any(test, feature = "testing"))]
2110 pub async fn uploaded_key_count(&self) -> Result<u64> {
2111 let olm_machine = self.client.olm_machine().await;
2112 let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
2113 Ok(olm_machine.uploaded_key_count().await?)
2114 }
2115
2116 pub(crate) async fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
2141 let bundle_receiver_task = if self.client.inner.enable_share_history_on_invite {
2146 Some(BundleReceiverTask::new(&self.client).await)
2147 } else {
2148 None
2149 };
2150
2151 let mut tasks = self.client.inner.e2ee.tasks.lock();
2152
2153 let this = self.clone();
2154
2155 tasks.setup_e2ee = Some(spawn(
2156 async move {
2157 this.update_verification_state().await;
2160
2161 if this.settings().auto_enable_cross_signing
2162 && let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await
2163 {
2164 error!("Couldn't bootstrap cross signing {e:?}");
2165 }
2166
2167 if let Err(e) = this.backups().setup_and_resume().await {
2168 error!("Couldn't setup and resume backups {e:?}");
2169 }
2170 if let Err(e) = this.recovery().setup().await {
2171 error!("Couldn't setup and resume recovery {e:?}");
2172 }
2173 }
2174 .instrument(Span::current()),
2175 ));
2176
2177 tasks.receive_historic_room_key_bundles = bundle_receiver_task;
2178
2179 self.setup_room_membership_session_discard_handler();
2180 }
2181
2182 pub async fn wait_for_e2ee_initialization_tasks(&self) {
2185 let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
2186
2187 if let Some(task) = task
2188 && let Err(err) = task.await
2189 {
2190 warn!("Error when initializing backups: {err}");
2191 }
2192 }
2193
2194 pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
2204 let olm = self.client.olm_machine().await;
2205 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
2206
2207 if let Some((request_id, request)) = olm.upload_device_keys().await? {
2208 self.client.keys_upload(&request_id, &request).await?;
2209
2210 let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
2211 self.client.keys_query(&request_id, request.device_keys).await?;
2212 }
2213
2214 Ok(())
2215 }
2216
2217 pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
2218 self.recovery().update_state_after_keys_query(response).await;
2219
2220 if let Some(user_id) = self.client.user_id() {
2222 let contains_own_device = response.device_keys.contains_key(user_id);
2223
2224 if contains_own_device {
2225 self.update_verification_state().await;
2226 }
2227 }
2228 }
2229
2230 async fn update_verification_state(&self) {
2231 match self.get_own_device().await {
2232 Ok(device) => {
2233 if let Some(device) = device {
2234 let is_verified = device.is_cross_signed_by_owner();
2235
2236 if is_verified {
2237 self.client.inner.verification_state.set(VerificationState::Verified);
2238 } else {
2239 self.client.inner.verification_state.set(VerificationState::Unverified);
2240 }
2241 } else {
2242 warn!("Couldn't find out own device in the store.");
2243 self.client.inner.verification_state.set(VerificationState::Unknown);
2244 }
2245 }
2246 Err(error) => {
2247 warn!("Failed retrieving own device: {error}");
2248 self.client.inner.verification_state.set(VerificationState::Unknown);
2249 }
2250 }
2251 }
2252
2253 fn setup_room_membership_session_discard_handler(&self) {
2273 let client = WeakClient::from_client(&self.client);
2274 self.client.add_event_handler(|ev: OriginalSyncRoomMemberEvent, room: Room| async move {
2275 let Some(client) = client.get() else {
2276 return;
2278 };
2279 let Some(user_id) = client.user_id() else {
2280 return;
2282 };
2283 let olm = client.olm_machine().await;
2284 let Some(olm) = olm.as_ref() else {
2285 warn!("Cannot discard session - Olm machine is not available");
2286 return;
2287 };
2288
2289 if matches!(
2290 ev.membership_change(),
2291 MembershipChange::Joined |
2292 MembershipChange::Invited |
2293 MembershipChange::KnockAccepted |
2294 MembershipChange::InvitationAccepted |
2295 MembershipChange::ProfileChanged { .. }
2296 ) || ev.sender == user_id {
2297 return;
2299 }
2300
2301 debug!(room_id = ?room.room_id(), member_id = ?ev.sender, "Discarding session as a user left the room");
2302
2303 if let Err(e) = olm.discard_room_key(room.room_id()).await {
2307 warn!(
2308 room_id = ?room.room_id(),
2309 "Error discarding room key after member leave: {e:?}"
2310 );
2311 }
2312 });
2313 }
2314
2315 #[cfg(feature = "experimental-send-custom-to-device")]
2325 pub async fn encrypt_and_send_raw_to_device(
2326 &self,
2327 recipient_devices: Vec<&Device>,
2328 event_type: &str,
2329 content: Raw<AnyToDeviceEventContent>,
2330 share_strategy: CollectStrategy,
2331 ) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
2332 let users = recipient_devices.iter().map(|device| device.user_id());
2333
2334 self.client.claim_one_time_keys(users).await?;
2338
2339 let olm = self.client.olm_machine().await;
2340 let olm = olm.as_ref().expect("Olm machine wasn't started");
2341
2342 let (requests, withhelds) = olm
2343 .encrypt_content_for_devices(
2344 recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
2345 event_type,
2346 &content
2347 .deserialize_as::<serde_json::Value>()
2348 .expect("Deserialize as Value will always work"),
2349 share_strategy,
2350 )
2351 .await?;
2352
2353 let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
2354
2355 withhelds.iter().for_each(|(d, _)| {
2357 failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
2358 });
2359
2360 for request in requests {
2362 let ruma_request = RumaToDeviceRequest::new_raw(
2363 request.event_type.clone(),
2364 request.txn_id.clone(),
2365 request.messages.clone(),
2366 );
2367
2368 let send_result = self
2369 .client
2370 .send_inner(ruma_request, Some(RequestConfig::short_retry()), Default::default())
2371 .await;
2372
2373 if send_result.is_err() {
2376 for (user_id, device_map) in request.messages {
2378 for device_id in device_map.keys() {
2379 match device_id {
2380 DeviceIdOrAllDevices::DeviceId(device_id) => {
2381 failures.push((user_id.clone(), device_id.to_owned()));
2382 }
2383 DeviceIdOrAllDevices::AllDevices => {
2384 }
2386 }
2387 }
2388 }
2389 }
2390 }
2391
2392 Ok(failures)
2393 }
2394}
2395
2396#[cfg(all(test, not(target_family = "wasm")))]
2397mod tests {
2398 use std::{
2399 ops::Not,
2400 str::FromStr,
2401 sync::{
2402 Arc,
2403 atomic::{AtomicBool, Ordering},
2404 },
2405 };
2406
2407 use matrix_sdk_test::{
2408 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
2409 event_factory::EventFactory,
2410 };
2411 use ruma::{
2412 event_id,
2413 events::{reaction::ReactionEventContent, relation::Annotation},
2414 user_id,
2415 };
2416 use serde_json::json;
2417 use wiremock::{
2418 Mock, MockServer, Request, ResponseTemplate,
2419 matchers::{header, method, path_regex},
2420 };
2421
2422 use crate::{
2423 Client, assert_next_matches_with_timeout,
2424 config::RequestConfig,
2425 encryption::{
2426 DuplicateOneTimeKeyErrorMessage, OAuthCrossSigningResetInfo, VerificationState,
2427 },
2428 test_utils::{
2429 client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
2430 },
2431 };
2432
2433 #[async_test]
2434 async fn test_reaction_sending() {
2435 let server = MockServer::start().await;
2436 let client = logged_in_client(Some(server.uri())).await;
2437
2438 let event_id = event_id!("$2:example.org");
2439
2440 Mock::given(method("GET"))
2441 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
2442 .and(header("authorization", "Bearer 1234"))
2443 .respond_with(
2444 ResponseTemplate::new(200)
2445 .set_body_json(EventFactory::new().room_encryption().into_content()),
2446 )
2447 .mount(&server)
2448 .await;
2449
2450 Mock::given(method("PUT"))
2451 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
2452 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2453 "event_id": event_id,
2454 })))
2455 .mount(&server)
2456 .await;
2457
2458 let f = EventFactory::new().sender(user_id!("@example:localhost"));
2459 let response = SyncResponseBuilder::default()
2460 .add_joined_room(
2461 JoinedRoomBuilder::default()
2462 .add_state_event(
2463 f.member(user_id!("@example:localhost")).display_name("example"),
2464 )
2465 .add_state_event(f.default_power_levels())
2466 .add_state_event(f.room_encryption()),
2467 )
2468 .build_sync_response();
2469
2470 client.base_client().receive_sync_response(response).await.unwrap();
2471
2472 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
2473 assert!(
2474 room.latest_encryption_state().await.expect("Getting encryption state").is_encrypted()
2475 );
2476
2477 let event_id = event_id!("$1:example.org");
2478 let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
2479 room.send(reaction).await.expect("Sending the reaction should not fail");
2480
2481 room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
2482 }
2483
2484 #[cfg(feature = "sqlite")]
2485 #[async_test]
2486 async fn test_generation_counter_invalidates_olm_machine() {
2487 use matrix_sdk_base::store::RoomLoadSettings;
2490 let tmp_dir = tempfile::tempdir().unwrap();
2491 let sqlite_path = tmp_dir.path().join("generation_counter_sqlite.db");
2492 let session = mock_matrix_session();
2493
2494 let client1 = Client::builder()
2495 .homeserver_url("http://localhost:1234")
2496 .request_config(RequestConfig::new().disable_retry())
2497 .sqlite_store(&sqlite_path, None)
2498 .build()
2499 .await
2500 .unwrap();
2501 client1
2502 .matrix_auth()
2503 .restore_session(session.clone(), RoomLoadSettings::default())
2504 .await
2505 .unwrap();
2506
2507 let client2 = Client::builder()
2508 .homeserver_url("http://localhost:1234")
2509 .request_config(RequestConfig::new().disable_retry())
2510 .sqlite_store(sqlite_path, None)
2511 .build()
2512 .await
2513 .unwrap();
2514 client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
2515
2516 let guard = client1.encryption().try_lock_store_once().await.unwrap();
2519 assert!(guard.is_none());
2520
2521 client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2522 client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
2523
2524 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2526 assert!(acquired1.is_some());
2527
2528 let initial_olm_machine =
2531 client1.olm_machine().await.clone().expect("must have an olm machine");
2532
2533 let decryption_key = matrix_sdk_base::crypto::store::types::BackupDecryptionKey::new();
2536 let backup_key = decryption_key.megolm_v1_public_key();
2537 backup_key.set_version("1".to_owned());
2538 initial_olm_machine
2539 .backup_machine()
2540 .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
2541 .await
2542 .expect("Should save");
2543
2544 initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
2545
2546 assert!(client1.encryption().backups().are_enabled().await);
2547
2548 let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2550 assert!(acquired2.is_none());
2551
2552 drop(acquired1);
2554
2555 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2557 assert!(acquired1.is_some());
2558
2559 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2561 assert!(initial_olm_machine.same_as(&olm_machine));
2562
2563 drop(acquired1);
2565
2566 let acquired2 = client2.encryption().spin_lock_store(None).await.unwrap();
2568 assert!(acquired2.is_some());
2569
2570 drop(acquired2);
2572
2573 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2575 assert!(acquired1.is_some());
2576
2577 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2579
2580 assert!(!initial_olm_machine.same_as(&olm_machine));
2581
2582 let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
2583 assert!(backup_key_new.decryption_key.is_some());
2584 assert_eq!(
2585 backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
2586 backup_key.to_base64()
2587 );
2588 assert!(client1.encryption().backups().are_enabled().await);
2589 }
2590
2591 #[cfg(feature = "sqlite")]
2592 #[async_test]
2593 async fn test_generation_counter_no_spurious_invalidation() {
2594 use matrix_sdk_base::store::RoomLoadSettings;
2597 let tmp_dir = tempfile::tempdir().unwrap();
2598 let sqlite_path = tmp_dir.path().join("generation_counter_no_spurious_invalidations.db");
2599 let session = mock_matrix_session();
2600
2601 let client = Client::builder()
2602 .homeserver_url("http://localhost:1234")
2603 .request_config(RequestConfig::new().disable_retry())
2604 .sqlite_store(&sqlite_path, None)
2605 .build()
2606 .await
2607 .unwrap();
2608 client
2609 .matrix_auth()
2610 .restore_session(session.clone(), RoomLoadSettings::default())
2611 .await
2612 .unwrap();
2613
2614 let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2615
2616 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2617
2618 let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2620 assert!(initial_olm_machine.same_as(&after_enabling_lock));
2621
2622 {
2623 let client2 = Client::builder()
2625 .homeserver_url("http://localhost:1234")
2626 .request_config(RequestConfig::new().disable_retry())
2627 .sqlite_store(sqlite_path, None)
2628 .build()
2629 .await
2630 .unwrap();
2631 client2
2632 .matrix_auth()
2633 .restore_session(session, RoomLoadSettings::default())
2634 .await
2635 .unwrap();
2636
2637 client2
2638 .encryption()
2639 .enable_cross_process_store_lock("client2".to_owned())
2640 .await
2641 .unwrap();
2642
2643 let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2644 assert!(guard.is_some());
2645
2646 drop(guard);
2647 }
2648
2649 {
2650 let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2651 assert!(acquired.is_some());
2652 }
2653
2654 let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2656 assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2657
2658 {
2659 let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2660 assert!(acquired.is_some());
2661 }
2662
2663 let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2665 assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2666 }
2667
2668 #[async_test]
2669 async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2670 let client = no_retry_test_client(None).await;
2672 let server = MockServer::start().await;
2673
2674 let mut verification_state = client.encryption().verification_state();
2676
2677 assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2679
2680 let keys_requested = Arc::new(AtomicBool::new(false));
2683 let inner_bool = keys_requested.clone();
2684
2685 Mock::given(method("GET"))
2686 .and(path_regex(
2687 r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2688 ))
2689 .respond_with(move |_req: &Request| {
2690 inner_bool.fetch_or(true, Ordering::SeqCst);
2691 ResponseTemplate::new(200).set_body_json(json!({}))
2692 })
2693 .mount(&server)
2694 .await;
2695
2696 set_client_session(&client).await;
2698
2699 assert!(keys_requested.load(Ordering::SeqCst).not());
2702 assert_next_matches_with_timeout!(verification_state, VerificationState::Unverified);
2703 }
2704
2705 #[test]
2706 fn test_oauth_reset_info_from_uiaa_info() {
2707 let auth_info = json!({
2708 "session": "dummy",
2709 "flows": [
2710 {
2711 "stages": [
2712 "org.matrix.cross_signing_reset"
2713 ]
2714 }
2715 ],
2716 "params": {
2717 "org.matrix.cross_signing_reset": {
2718 "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2719 }
2720 },
2721 "msg": "To reset..."
2722 });
2723
2724 let auth_info = serde_json::from_value(auth_info)
2725 .expect("We should be able to deserialize the UiaaInfo");
2726 OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2727 .expect("We should be able to fetch the cross-signing reset info from the auth info");
2728 }
2729
2730 #[test]
2731 fn test_duplicate_one_time_key_error_parsing() {
2732 let message = concat!(
2733 r#"One time key signed_curve25519:AAAAAAAAAAA already exists. "#,
2734 r#"Old key: {"key":"dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs","#,
2735 r#""signatures":{"@example:matrix.org":{"ed25519:AAAAAAAAAA":""#,
2736 r#"Fk45zHAbrd+1j9wZXLjL2Y/+DU/Mnz9yuvlfYBOOT7qExN2Jdud+5BAuNs8nZ/caS4wTF39Kg3zQpzaGERoCBg"}}};"#,
2737 r#" new key: {'key': 'CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno', "#,
2738 r#"'signatures': {'@example:matrix.org': {'ed25519:AAAAAAAAAA': "#,
2739 r#"'BQ9Gp0p+6srF+c8OyruqKKd9R4yaub3THYAyyBB/7X/rG8BwcAqFynzl1aGyFYun4Q+087a5OSiglCXI+/kQAA'}}}"#
2740 );
2741 let message = DuplicateOneTimeKeyErrorMessage::from_str(message)
2742 .expect("We should be able to parse the error message");
2743
2744 assert_eq!(message.old_key.to_base64(), "dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs");
2745 assert_eq!(message.new_key.to_base64(), "CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno");
2746
2747 DuplicateOneTimeKeyErrorMessage::from_str("One time key already exists.")
2748 .expect_err("We shouldn't be able to parse an incomplete error message");
2749 }
2750
2751 fn devices_to_verify_against_keys_query_response(
2755 devices: Vec<serde_json::Value>,
2756 ) -> serde_json::Value {
2757 let device_keys: serde_json::Map<String, serde_json::Value> = devices
2758 .into_iter()
2759 .map(|device| (device.get("device_id").unwrap().as_str().unwrap().to_owned(), device))
2760 .collect();
2761 json!({
2762 "device_keys": {
2763 "@example:localhost": device_keys,
2764 },
2765 "master_keys": {
2766 "@example:localhost": {
2767 "keys": {
2768 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU",
2769 },
2770 "usage": ["master"],
2771 "user_id": "@example:localhost",
2772 },
2773 },
2774 "self_signing_keys": {
2775 "@example:localhost": {
2776 "keys": {
2777 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM",
2778 },
2779 "usage": ["self_signing"],
2780 "user_id": "@example:localhost",
2781 "signatures": {
2782 "@example:localhost": {
2783 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "etO1bB+rCk+TQ/FcjQ8eWu/RsRNQNNQ1Ek+PD6//j8yz6igRjfvuHZaMvr/quAFrirfgExph2TdOwlDgN5bFCQ",
2784 },
2785 },
2786 },
2787 },
2788 "user_signing_keys": {
2789 "@example:localhost": {
2790 "keys": {
2791 "ed25519:CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0": "CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0",
2792 },
2793 "usage": ["user_signing"],
2794 "user_id": "@example:localhost",
2795 "signatures": {
2796 "@example:localhost": {
2797 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "E/DFi/hQTIb/7eSB+HbCXeTLFaLjqWHzLO9GwjL1qdhfO7ew4p6YdtXSH3T2YYr1dKCPteH/4nMYVwOhww2CBg",
2798 },
2799 },
2800 },
2801 }
2802 })
2803 }
2804
2805 #[async_test]
2808 async fn test_devices_to_verify_against_no_devices() {
2811 let server = MockServer::start().await;
2812 let client = logged_in_client(Some(server.uri())).await;
2813
2814 Mock::given(method("POST"))
2815 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2816 .respond_with(
2817 ResponseTemplate::new(200)
2818 .set_body_json(devices_to_verify_against_keys_query_response(vec![])),
2819 )
2820 .mount(&server)
2821 .await;
2822
2823 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2824 }
2825
2826 #[async_test]
2827 async fn test_devices_to_verify_against_cross_signed() {
2830 let server = MockServer::start().await;
2831 let client = logged_in_client(Some(server.uri())).await;
2832
2833 Mock::given(method("POST"))
2834 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2835 .respond_with(ResponseTemplate::new(200).set_body_json(
2836 devices_to_verify_against_keys_query_response(vec![
2837 json!({
2838 "algorithms": [
2839 "m.olm.v1.curve25519-aes-sha2",
2840 "m.megolm.v1.aes-sha2",
2841 ],
2842 "user_id": "@example:localhost",
2843 "device_id": "SIGNEDDEVICE",
2844 "keys": {
2845 "curve25519:SIGNEDDEVICE": "o1LqUtH/sqd3WF+BB2Qr77uw3sDmZhMOz68/IV9aHxs",
2846 "ed25519:SIGNEDDEVICE": "iVoEfMOoUqxXVMLdpZCOgvQuCrT3/kQWkBmB3Phi/lo",
2847 },
2848 "signatures": {
2849 "@example:localhost": {
2850 "ed25519:SIGNEDDEVICE": "C7yRu1fNrdD2EobVdtANMqk3LBtWtTRWrIU22xVS8/Om1kmA/luzek64R3N6JsZhYczVmZYBKhUC9kRvHHwOBg",
2851 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "frfh2HP28GclmGvwTic00Fj4nZCvm4RlRA6U56mnD5920hOi04+L055ojzp6ybZXvC/GQYfyTHwQXlUN1nvxBA",
2852 },
2853 },
2854 })
2855 ])
2856 ))
2857 .mount(&server)
2858 .await;
2859
2860 assert!(client.encryption().has_devices_to_verify_against().await.unwrap());
2861 }
2862
2863 #[async_test]
2864 async fn test_devices_to_verify_against_dehydrated_and_unsigned() {
2867 let server = MockServer::start().await;
2868 let client = logged_in_client(Some(server.uri())).await;
2869 let user_id = client.user_id().unwrap();
2870 let olm_machine = client.olm_machine().await;
2871 let olm_machine = olm_machine.as_ref().unwrap();
2872
2873 Mock::given(method("POST"))
2874 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2875 .respond_with(ResponseTemplate::new(200).set_body_json(
2876 devices_to_verify_against_keys_query_response(vec![
2877 json!({
2878 "algorithms": [
2879 "m.olm.v1.curve25519-aes-sha2",
2880 "m.megolm.v1.aes-sha2",
2881 ],
2882 "user_id": "@example:localhost",
2883 "device_id": "DEHYDRATEDDEVICE",
2884 "keys": {
2885 "curve25519:DEHYDRATEDDEVICE": "XOn5VguAgokZ3p9mBz2yOB395fn6j75G8jIPcXEWQGY",
2886 "ed25519:DEHYDRATEDDEVICE": "4GG5xmBT7z4rgUgmWNlKZ+ABE3QlGgTorF+luCnKfYI",
2887 },
2888 "dehydrated": true,
2889 "signatures": {
2890 "@example:localhost": {
2891 "ed25519:DEHYDRATEDDEVICE": "+OMasB7nzVlMV+zRDxkh4h8h/Q0bY42P1SPv7X2IURIelT5G+d+AYSmg30N4maphxEDBqt/vI8/lIr71exc3Dg",
2892 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "8DzynAgbYgXX1Md5d4Vw91Zstpoi4dpG7levFeVhi4psCAWuBnV76Qu1s2TGjQQ0CLDXEqcxxuX9X4eUK5TGCg",
2893 },
2894 },
2895 }),
2896 json!({
2897 "algorithms": [
2898 "m.olm.v1.curve25519-aes-sha2",
2899 "m.megolm.v1.aes-sha2",
2900 ],
2901 "user_id": "@example:localhost",
2902 "device_id": "UNSIGNEDDEVICE",
2903 "keys": {
2904 "curve25519:UNSIGNEDDEVICE": "mMby6NpprkHxj+ONfO9Z5lBqVUHJBMkrPFSNJhogBkg",
2905 "ed25519:UNSIGNEDDEVICE": "Zifq39ZDrlIaSRf0Hh22owEqXCPE+1JSSgs6LDlubwQ",
2906 },
2907 "signatures": {
2908 "@example:localhost": {
2909 "ed25519:UNSIGNEDDEVICE": "+L29RoDKoTufPGm/Bae65KHno7Z1H7GYhxSKpB4RQZRS7NrR29AMW1PVhEsIozYuDVEFuMZ0L8H3dlcaHxagBA",
2910 },
2911 },
2912 }),
2913 ])
2914 ))
2915 .mount(&server)
2916 .await;
2917
2918 let (request_id, request) = olm_machine.query_keys_for_users([user_id]);
2919 client.keys_query(&request_id, request.device_keys).await.unwrap();
2920
2921 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2922 }
2923
2924 #[cfg(feature = "experimental-send-custom-to-device")]
2925 mod resolve_recipient_devices {
2926 use matrix_sdk_test::async_test;
2927 use ruma::{
2928 OwnedDeviceId, device_id, owned_device_id, to_device::DeviceIdOrAllDevices, user_id,
2929 };
2930
2931 use super::super::Device;
2932 use crate::{Client, test_utils::mocks::MatrixMockServer};
2933
2934 const BOB_FIRST_DEVICE: &str = "B0B0B0B0B";
2935 const BOB_SECOND_DEVICE: &str = "B0B2B0B2";
2936 const BOB_THIRD_DEVICE: &str = "B0B3B0B3";
2937
2938 async fn alice_and_bob_with_two_bob_devices() -> (MatrixMockServer, Client, Client) {
2944 let server = MatrixMockServer::new().await;
2945 server.mock_crypto_endpoints_preset().await;
2946
2947 let (alice, bob) = server.set_up_alice_and_bob_for_encryption().await;
2948 assert_eq!(bob.device_id().unwrap(), device_id!(BOB_FIRST_DEVICE));
2949
2950 server
2951 .set_up_new_device_for_encryption(&bob, device_id!(BOB_SECOND_DEVICE), vec![&alice])
2952 .await;
2953
2954 server
2956 .mock_sync()
2957 .ok_and_run(&alice, |builder| {
2958 builder.add_change_device(bob.user_id().unwrap());
2959 })
2960 .await;
2961
2962 (server, alice, bob)
2963 }
2964
2965 fn device_ids(devices: Vec<Device>) -> Vec<OwnedDeviceId> {
2968 let mut device_ids: Vec<_> =
2969 devices.into_iter().map(|d| d.device_id().to_owned()).collect();
2970 device_ids.sort();
2971 device_ids
2972 }
2973
2974 #[async_test]
2975 async fn test_all_devices_resolves_to_every_known_device() {
2976 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
2977
2978 let (devices, unknown_devices) = alice
2979 .resolve_recipient_devices(
2980 bob.user_id().unwrap(),
2981 vec![DeviceIdOrAllDevices::AllDevices],
2982 )
2983 .await
2984 .unwrap();
2985
2986 assert_eq!(
2987 device_ids(devices),
2988 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
2989 );
2990 assert!(unknown_devices.is_empty(), "`AllDevices` can't reference an unknown device");
2991 }
2992
2993 #[async_test]
2994 async fn test_explicit_device_ids_are_filtered_to_the_requested_ones() {
2995 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
2996
2997 let (devices, unknown_devices) = alice
2998 .resolve_recipient_devices(
2999 bob.user_id().unwrap(),
3000 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_SECOND_DEVICE))],
3001 )
3002 .await
3003 .unwrap();
3004
3005 assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_SECOND_DEVICE)]);
3006 assert!(unknown_devices.is_empty());
3007 }
3008
3009 #[async_test]
3011 async fn test_several_explicit_device_ids_are_all_resolved() {
3012 let (server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3013
3014 server
3015 .set_up_new_device_for_encryption(&bob, device_id!(BOB_THIRD_DEVICE), vec![&alice])
3016 .await;
3017
3018 server
3020 .mock_sync()
3021 .ok_and_run(&alice, |builder| {
3022 builder.add_change_device(bob.user_id().unwrap());
3023 })
3024 .await;
3025
3026 let (devices, unknown_devices) = alice
3027 .resolve_recipient_devices(
3028 bob.user_id().unwrap(),
3029 vec![
3030 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3031 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_THIRD_DEVICE)),
3032 ],
3033 )
3034 .await
3035 .unwrap();
3036
3037 assert_eq!(
3038 device_ids(devices),
3039 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_THIRD_DEVICE)],
3040 "the second device was not requested and must be left out"
3041 );
3042 assert!(unknown_devices.is_empty());
3043 }
3044
3045 #[async_test]
3046 async fn test_unknown_device_is_reported_back() {
3047 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3048
3049 let (devices, unknown_devices) = alice
3050 .resolve_recipient_devices(
3051 bob.user_id().unwrap(),
3052 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE"))],
3053 )
3054 .await
3055 .unwrap();
3056
3057 assert!(devices.is_empty());
3058 assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3059 }
3060
3061 #[async_test]
3064 async fn test_known_and_unknown_devices_are_split() {
3065 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3066
3067 let (devices, unknown_devices) = alice
3068 .resolve_recipient_devices(
3069 bob.user_id().unwrap(),
3070 vec![
3071 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3072 DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3073 ],
3074 )
3075 .await
3076 .unwrap();
3077
3078 assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3079 assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3080 }
3081
3082 #[async_test]
3086 async fn test_all_devices_takes_precedence_over_explicit_device_ids() {
3087 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3088
3089 let (devices, unknown_devices) = alice
3090 .resolve_recipient_devices(
3091 bob.user_id().unwrap(),
3092 vec![
3093 DeviceIdOrAllDevices::AllDevices,
3094 DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3095 ],
3096 )
3097 .await
3098 .unwrap();
3099
3100 assert_eq!(
3101 device_ids(devices),
3102 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
3103 );
3104 assert!(unknown_devices.is_empty());
3105 }
3106
3107 #[async_test]
3110 async fn test_empty_recipient_list_resolves_to_no_device() {
3111 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3112
3113 let (devices, unknown_devices) =
3114 alice.resolve_recipient_devices(bob.user_id().unwrap(), vec![]).await.unwrap();
3115
3116 assert!(devices.is_empty());
3117 assert!(unknown_devices.is_empty());
3118 }
3119
3120 #[async_test]
3123 async fn test_unknown_user_has_no_device() {
3124 let (_server, alice, _bob) = alice_and_bob_with_two_bob_devices().await;
3125 let unknown_user_id = user_id!("@carol:example.org");
3126
3127 let (devices, unknown_devices) = alice
3128 .resolve_recipient_devices(
3129 unknown_user_id,
3130 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE))],
3131 )
3132 .await
3133 .unwrap();
3134
3135 assert!(devices.is_empty());
3136 assert_eq!(unknown_devices, vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3137
3138 let (devices, unknown_devices) = alice
3141 .resolve_recipient_devices(unknown_user_id, vec![DeviceIdOrAllDevices::AllDevices])
3142 .await
3143 .unwrap();
3144
3145 assert!(devices.is_empty());
3146 assert!(unknown_devices.is_empty());
3147 }
3148 }
3149}