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 {
784 let already_reported = self
785 .state_store()
786 .get_kv_data(StateStoreDataKey::OneTimeKeyAlreadyUploaded)
787 .await?
788 .is_some();
789
790 if message.starts_with("One time key") && !already_reported {
791 let error_message =
792 DuplicateOneTimeKeyErrorMessage::from_str(message);
793
794 if let Ok(message) = &error_message {
795 error!(
796 sentry = true,
797 old_key = %message.old_key,
798 new_key = %message.new_key,
799 "Duplicate one-time keys have been uploaded"
800 );
801 } else {
802 error!(
803 sentry = true,
804 "Duplicate one-time keys have been uploaded"
805 );
806 }
807
808 self.state_store()
809 .set_kv_data(
810 StateStoreDataKey::OneTimeKeyAlreadyUploaded,
811 StateStoreDataValue::OneTimeKeyAlreadyUploaded,
812 )
813 .await?;
814
815 if let Err(e) = self
816 .inner
817 .duplicate_key_upload_error_sender
818 .send(error_message.ok())
819 {
820 error!(
821 "Failed to dispatch duplicate key upload error notification: {}",
822 e
823 );
824 }
825 }
826 }
827 }
828 }
829 _ => {}
830 }
831
832 response?;
833 }
834 }
835 AnyOutgoingRequest::ToDeviceRequest(request) => {
836 let response = self.send_to_device(request).await?;
837 self.mark_request_as_sent(r.request_id(), &response).await?;
838 }
839 AnyOutgoingRequest::SignatureUpload(request) => {
840 let response = self.send(request.clone()).await?;
841 self.mark_request_as_sent(r.request_id(), &response).await?;
842 }
843 AnyOutgoingRequest::RoomMessage(request) => {
844 let response = self.room_send_helper(request).await?;
845 self.mark_request_as_sent(r.request_id(), &response).await?;
846 }
847 AnyOutgoingRequest::KeysClaim(request) => {
848 let response = self.send(request.clone()).await?;
849 self.mark_request_as_sent(r.request_id(), &response).await?;
850 }
851 }
852
853 Ok(())
854 }
855
856 #[instrument(skip_all)]
857 pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
858 const MAX_CONCURRENT_REQUESTS: usize = 20;
859
860 if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
863 warn!("Error while claiming one-time keys {:?}", e);
864 }
865
866 let outgoing_requests = stream::iter(
867 self.olm_machine()
868 .await
869 .as_ref()
870 .ok_or(Error::NoOlmMachine)?
871 .outgoing_requests()
872 .await?,
873 )
874 .map(|r| self.send_outgoing_request(r));
875
876 let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
877
878 requests
879 .for_each(|r| async move {
880 match r {
881 Ok(_) => (),
882 Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
883 }
884 })
885 .await;
886
887 Ok(())
888 }
889}
890
891#[cfg(feature = "experimental-send-custom-to-device")]
892impl Client {
893 pub async fn send_encrypted_to_device(
921 &self,
922 event_type: &ToDeviceEventType,
923 recipients: BTreeMap<OwnedUserId, Vec<DeviceIdOrAllDevices>>,
924 content: Raw<AnyToDeviceEventContent>,
925 ) -> Result<BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>> {
926 let mut failures: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>> = BTreeMap::new();
927 let mut recipient_devices = Vec::<_>::new();
928
929 for (user_id, recipient_device_ids) in recipients {
931 let (devices, unknown_devices) =
932 self.resolve_recipient_devices(&user_id, recipient_device_ids).await?;
933 recipient_devices.extend(devices);
934 if !unknown_devices.is_empty() {
935 failures.insert(user_id, unknown_devices);
936 }
937 }
938
939 if !recipient_devices.is_empty() {
940 let encrypt_and_send_failures = self
941 .encryption()
942 .encrypt_and_send_raw_to_device(
943 recipient_devices.iter().collect(),
944 &event_type.to_string(),
945 content,
946 CollectStrategy::AllDevices,
947 )
948 .await?;
949
950 for (user_id, device_id) in encrypt_and_send_failures {
951 failures.entry(user_id).or_default().push(device_id)
952 }
953 }
954
955 Ok(failures)
956 }
957
958 async fn resolve_recipient_devices(
967 &self,
968 user_id: &UserId,
969 recipient_device_ids: Vec<DeviceIdOrAllDevices>,
970 ) -> Result<(Vec<Device>, Vec<OwnedDeviceId>)> {
971 let user_devices = self.encryption().get_user_devices(user_id).await?;
972
973 if recipient_device_ids.contains(&DeviceIdOrAllDevices::AllDevices) {
974 let devices: Vec<_> = user_devices.devices().collect();
977
978 if devices.is_empty() {
979 warn!(
980 "Recipient list contains `AllDevices` but no devices found for user {user_id}."
981 );
982 }
983 if recipient_device_ids.len() > 1 {
984 warn!(
985 "The recipient_device_ids list for {user_id} contains both `AllDevices` and explicit `DeviceId` entries. Only consider `AllDevices`",
986 );
987 }
988
989 Ok((devices, Vec::new()))
990 } else {
991 let (found_device_ids, devices): (BTreeSet<_>, Vec<_>) = user_devices
994 .devices()
995 .map(|device| (device.device_id().to_owned(), device))
996 .filter(|(device_id, _)| {
997 recipient_device_ids
998 .contains(&DeviceIdOrAllDevices::DeviceId(device_id.clone()))
999 })
1000 .unzip();
1001
1002 let requested_device_ids: BTreeSet<_> = recipient_device_ids
1003 .into_iter()
1004 .filter_map(|d| as_variant!(d, DeviceIdOrAllDevices::DeviceId))
1005 .collect();
1006
1007 let missing_devices =
1010 requested_device_ids.difference(&found_device_ids).map(ToOwned::to_owned).collect();
1011
1012 Ok((devices, missing_devices))
1013 }
1014 }
1015}
1016
1017#[cfg(any(feature = "testing", test))]
1018impl Client {
1019 pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
1021 self.olm_machine().await
1022 }
1023
1024 pub fn abort_bundle_receiver_task(&self) {
1026 let tasks = self.inner.e2ee.tasks.lock();
1027 if let Some(task) = tasks.receive_historic_room_key_bundles.as_ref() {
1028 task.abort()
1029 }
1030 }
1031}
1032
1033#[derive(Debug, Clone)]
1037pub struct Encryption {
1038 client: Client,
1040}
1041
1042impl Encryption {
1043 pub(crate) fn new(client: Client) -> Self {
1044 Self { client }
1045 }
1046
1047 pub(crate) fn settings(&self) -> EncryptionSettings {
1049 self.client.inner.e2ee.encryption_settings
1050 }
1051
1052 pub async fn ed25519_key(&self) -> Option<String> {
1055 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
1056 }
1057
1058 pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
1060 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
1061 }
1062
1063 pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
1065 match self.get_own_device().await {
1066 Ok(Some(device)) => device.first_time_seen_ts(),
1067 _ => MilliSecondsSinceUnixEpoch::now(),
1069 }
1070 }
1071
1072 pub async fn import_secrets_bundle(
1085 &self,
1086 bundle: &SecretsBundle,
1087 ) -> Result<(), BundleImportError> {
1088 self.import_secrets_bundle_impl(bundle).await?;
1089
1090 self.ensure_device_keys_upload().await?;
1093 self.wait_for_e2ee_initialization_tasks().await;
1094
1095 if !self.backups().are_enabled().await {
1100 self.backups().maybe_resume_backups().await?;
1101 }
1102
1103 Ok(())
1104 }
1105
1106 pub(crate) async fn import_secrets_bundle_impl(
1107 &self,
1108 bundle: &SecretsBundle,
1109 ) -> Result<(), SecretImportError> {
1110 let olm_machine = self.client.olm_machine().await;
1111 let olm_machine =
1112 olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
1113
1114 olm_machine.store().import_secrets_bundle(bundle).await
1115 }
1116
1117 pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
1122 let olm = self.client.olm_machine().await;
1123 let machine = olm.as_ref()?;
1124 Some(machine.cross_signing_status().await)
1125 }
1126
1127 pub async fn has_devices_to_verify_against(&self) -> Result<bool> {
1133 let olm_machine = self.client.olm_machine().await;
1134 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1135 let user_id = olm_machine.user_id();
1136
1137 self.ensure_initial_key_query().await?;
1138
1139 let devices = self.get_user_devices(user_id).await?;
1140
1141 let ret = devices.devices().any(|device| {
1142 device.is_cross_signed_by_owner()
1143 && device.curve25519_key().is_some()
1144 && !device.is_dehydrated()
1145 });
1146
1147 Ok(ret)
1148 }
1149
1150 pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
1155 if let Some(machine) = self.client.olm_machine().await.as_ref() {
1156 machine.tracked_users().await
1157 } else {
1158 Ok(HashSet::new())
1159 }
1160 }
1161
1162 pub fn verification_state(&self) -> Subscriber<VerificationState> {
1185 self.client.inner.verification_state.subscribe_reset()
1186 }
1187
1188 pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
1190 let olm = self.client.olm_machine().await;
1191 let olm = olm.as_ref()?;
1192 #[allow(clippy::bind_instead_of_map)]
1193 olm.get_verification(user_id, flow_id).and_then(|v| match v {
1194 matrix_sdk_base::crypto::Verification::SasV1(sas) => {
1195 Some(SasVerification { inner: sas, client: self.client.clone() }.into())
1196 }
1197 #[cfg(feature = "qrcode")]
1198 matrix_sdk_base::crypto::Verification::QrV1(qr) => {
1199 Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
1200 }
1201 _ => None,
1202 })
1203 }
1204
1205 pub async fn get_verification_request(
1208 &self,
1209 user_id: &UserId,
1210 flow_id: impl AsRef<str>,
1211 ) -> Option<VerificationRequest> {
1212 let olm = self.client.olm_machine().await;
1213 let olm = olm.as_ref()?;
1214
1215 olm.get_verification_request(user_id, flow_id)
1216 .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
1217 }
1218
1219 pub async fn get_device(
1253 &self,
1254 user_id: &UserId,
1255 device_id: &DeviceId,
1256 ) -> Result<Option<Device>, CryptoStoreError> {
1257 let olm = self.client.olm_machine().await;
1258 let Some(machine) = olm.as_ref() else { return Ok(None) };
1259 let device = machine.get_device(user_id, device_id, None).await?;
1260 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1261 }
1262
1263 pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
1270 let olm = self.client.olm_machine().await;
1271 let Some(machine) = olm.as_ref() else { return Ok(None) };
1272 let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
1273 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1274 }
1275
1276 pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
1302 let devices = self
1303 .client
1304 .olm_machine()
1305 .await
1306 .as_ref()
1307 .ok_or(Error::NoOlmMachine)?
1308 .get_user_devices(user_id, None)
1309 .await?;
1310
1311 Ok(UserDevices { inner: devices, client: self.client.clone() })
1312 }
1313
1314 pub async fn get_user_identity(
1350 &self,
1351 user_id: &UserId,
1352 ) -> Result<Option<UserIdentity>, CryptoStoreError> {
1353 let olm = self.client.olm_machine().await;
1354 let Some(olm) = olm.as_ref() else { return Ok(None) };
1355 let identity = olm.get_identity(user_id, None).await?;
1356
1357 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1358 }
1359
1360 pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
1398 let olm = self.client.olm_machine().await;
1399 let Some(olm) = olm.as_ref() else { return Ok(None) };
1400
1401 let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
1402 self.client.keys_query(&request_id, request.device_keys).await?;
1403
1404 let identity = olm.get_identity(user_id, None).await?;
1405 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1406 }
1407
1408 pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates> + use<>> {
1439 let olm = self.client.olm_machine().await;
1440 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1441 let client = self.client.to_owned();
1442
1443 Ok(olm
1444 .store()
1445 .devices_stream()
1446 .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1447 }
1448
1449 pub async fn user_identities_stream(
1477 &self,
1478 ) -> Result<impl Stream<Item = IdentityUpdates> + use<>> {
1479 let olm = self.client.olm_machine().await;
1480 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1481 let client = self.client.to_owned();
1482
1483 Ok(olm
1484 .store()
1485 .user_identities_stream()
1486 .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1487 }
1488
1489 pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1528 let olm = self.client.olm_machine().await;
1529 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1530
1531 let CrossSigningBootstrapRequests {
1532 upload_signing_keys_req,
1533 upload_keys_req,
1534 upload_signatures_req,
1535 } = olm.bootstrap_cross_signing(false).await?;
1536
1537 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1538 auth: auth_data,
1539 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1540 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1541 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1542 });
1543
1544 if let Some(req) = upload_keys_req {
1545 self.client.send_outgoing_request(req).await?;
1546 }
1547 self.client.send(upload_signing_keys_req).await?;
1548 self.client.send(upload_signatures_req).await?;
1549
1550 Ok(())
1551 }
1552
1553 pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1594 let olm = self.client.olm_machine().await;
1595 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1596
1597 let CrossSigningBootstrapRequests {
1598 upload_keys_req,
1599 upload_signing_keys_req,
1600 upload_signatures_req,
1601 } = olm.bootstrap_cross_signing(true).await?;
1602
1603 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1604 auth: None,
1605 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1606 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1607 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1608 });
1609
1610 if let Some(req) = upload_keys_req {
1611 self.client.send_outgoing_request(req).await?;
1612 }
1613
1614 if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1615 if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1616 let client = self.client.clone();
1617
1618 Ok(Some(CrossSigningResetHandle::new(
1619 client,
1620 upload_signing_keys_req,
1621 upload_signatures_req,
1622 auth_type,
1623 )))
1624 } else {
1625 Err(error.into())
1626 }
1627 } else {
1628 self.client.send(upload_signatures_req).await?;
1629
1630 Ok(None)
1631 }
1632 }
1633
1634 async fn ensure_initial_key_query(&self) -> Result<()> {
1637 let olm_machine = self.client.olm_machine().await;
1638 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1639
1640 let user_id = olm_machine.user_id();
1641
1642 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1643 let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1644 self.client.keys_query(&request_id, request.device_keys).await?;
1645 }
1646
1647 Ok(())
1648 }
1649
1650 pub async fn bootstrap_cross_signing_if_needed(
1697 &self,
1698 auth_data: Option<AuthData>,
1699 ) -> Result<()> {
1700 let olm_machine = self.client.olm_machine().await;
1701 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1702 let user_id = olm_machine.user_id();
1703
1704 self.ensure_initial_key_query().await?;
1705
1706 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1707 self.bootstrap_cross_signing(auth_data).await?;
1708 }
1709
1710 Ok(())
1711 }
1712
1713 #[cfg(not(target_family = "wasm"))]
1765 pub async fn export_room_keys(
1766 &self,
1767 path: PathBuf,
1768 passphrase: &str,
1769 predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1770 ) -> Result<()> {
1771 let olm = self.client.olm_machine().await;
1772 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1773
1774 let keys = olm.store().export_room_keys(predicate).await?;
1775 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1776
1777 let encrypt = move || -> Result<()> {
1778 let export: String =
1779 matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1780 let mut file = std::fs::File::create(path)?;
1781 file.write_all(&export.into_bytes())?;
1782 Ok(())
1783 };
1784
1785 let task = tokio::task::spawn_blocking(encrypt);
1786 task.await.expect("Task join error")
1787 }
1788
1789 #[cfg(not(target_family = "wasm"))]
1827 pub async fn import_room_keys(
1828 &self,
1829 path: PathBuf,
1830 passphrase: &str,
1831 ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1832 let olm = self.client.olm_machine().await;
1833 let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1834 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1835
1836 let decrypt = move || {
1837 let file = std::fs::File::open(path)?;
1838 matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1839 };
1840
1841 let task = tokio::task::spawn_blocking(decrypt);
1842 let import = task.await.expect("Task join error")?;
1843
1844 let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1845
1846 self.backups().maybe_trigger_backup();
1847
1848 Ok(ret)
1849 }
1850
1851 pub async fn room_keys_received_stream(
1882 &self,
1883 ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<>>
1884 {
1885 let olm = self.client.olm_machine().await;
1886 let olm = olm.as_ref()?;
1887
1888 Some(olm.store().room_keys_received_stream())
1889 }
1890
1891 pub async fn historic_room_key_stream(
1922 &self,
1923 ) -> Option<impl Stream<Item = RoomKeyBundleInfo> + use<>> {
1924 let olm = self.client.olm_machine().await;
1925 let olm = olm.as_ref()?;
1926
1927 Some(olm.store().historic_room_key_stream())
1928 }
1929
1930 pub fn secret_storage(&self) -> SecretStorage {
1932 SecretStorage { client: self.client.to_owned() }
1933 }
1934
1935 pub fn backups(&self) -> Backups {
1937 Backups { client: self.client.to_owned() }
1938 }
1939
1940 pub fn recovery(&self) -> Recovery {
1942 Recovery { client: self.client.to_owned() }
1943 }
1944
1945 pub fn dehydrated_devices(&self) -> dehydrated_devices::DehydratedDevices {
1953 dehydrated_devices::DehydratedDevices { client: self.client.to_owned() }
1954 }
1955
1956 pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
1968 if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1970 let prev_holder = prev_lock.lock_holder();
1971 if prev_holder.is_some() && prev_holder.unwrap() == lock_value {
1972 return Ok(());
1973 }
1974 warn!(
1975 "Recreating cross-process store lock with a different holder value: \
1976 prev was {prev_holder:?}, new is {lock_value}"
1977 );
1978 }
1979
1980 let olm_machine = self.client.base_client().olm_machine().await;
1981 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1982
1983 let lock = olm_machine.store().create_store_lock(
1984 "cross_process_lock".to_owned(),
1985 CrossProcessLockConfig::multi_process(lock_value.to_owned()),
1986 );
1987
1988 {
1993 let lock_result = lock.try_lock_once().await?;
1994
1995 if lock_result.is_ok() {
1996 olm_machine
1997 .initialize_crypto_store_generation(
1998 &self.client.locks().crypto_store_generation,
1999 )
2000 .await?;
2001 }
2002 }
2003
2004 self.client
2005 .locks()
2006 .cross_process_crypto_store_lock
2007 .set(lock)
2008 .map_err(|_| Error::BadCryptoStoreState)?;
2009
2010 Ok(())
2011 }
2012
2013 #[instrument(skip(self), fields(olm_machine_new_generation, olm_machine_generation))]
2018 async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
2019 let olm_machine_guard = self.client.olm_machine().await;
2020 if let Some(olm_machine) = olm_machine_guard.as_ref() {
2021 let (new_gen, generation_number) = olm_machine
2022 .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
2023 .await?;
2024
2025 Span::current()
2026 .record("olm_machine_new_generation", new_gen)
2027 .record("olm_machine_generation", generation_number);
2028 debug!("OlmMachine generation maintained in CryptoStore");
2029
2030 if new_gen {
2032 drop(olm_machine_guard);
2034 self.client.base_client().regenerate_olm(None).await?;
2036 }
2037 Ok(generation_number)
2038 } else {
2039 warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
2044 Ok(0)
2045 }
2046 }
2047
2048 pub async fn spin_lock_store(
2056 &self,
2057 max_backoff: Option<u32>,
2058 ) -> Result<Option<CrossProcessLockGuard>, Error> {
2059 self.lock_store(async move |lock| lock.spin_lock(max_backoff).await).await
2060 }
2061
2062 pub async fn try_lock_store_once(&self) -> Result<Option<CrossProcessLockGuard>, Error> {
2070 match self.lock_store(CrossProcessLock::try_lock_once).await {
2071 Err(Error::CrossProcessLockError(e))
2072 if matches!(*e, CrossProcessLockError::Unobtained(_)) =>
2073 {
2074 Ok(None)
2075 }
2076 other => other,
2077 }
2078 }
2079
2080 pub async fn lock_store<F: AcquireCrossProcessLockFn<LockableCryptoStore>>(
2087 &self,
2088 acquire: F,
2089 ) -> Result<Option<CrossProcessLockGuard>, Error> {
2090 let wrap_err = |e: CryptoStoreError| {
2091 Error::CrossProcessLockError(Box::new(CrossProcessLockError::TryLock(Arc::new(e))))
2092 };
2093 if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
2094 let guard = acquire(lock).await.map_err(wrap_err)??;
2095 let _ = self.on_lock_newly_acquired().await?;
2096 Ok(Some(guard.into_guard()))
2097 } else {
2098 Ok(None)
2099 }
2100 }
2101
2102 #[cfg(any(test, feature = "testing"))]
2104 pub async fn uploaded_key_count(&self) -> Result<u64> {
2105 let olm_machine = self.client.olm_machine().await;
2106 let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
2107 Ok(olm_machine.uploaded_key_count().await?)
2108 }
2109
2110 pub(crate) async fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
2134 let bundle_receiver_task = if self.client.inner.enable_share_history_on_invite {
2138 Some(BundleReceiverTask::new(&self.client).await)
2139 } else {
2140 None
2141 };
2142
2143 let mut tasks = self.client.inner.e2ee.tasks.lock();
2144
2145 let this = self.clone();
2146
2147 tasks.setup_e2ee = Some(spawn(
2148 async move {
2149 this.update_verification_state().await;
2152
2153 if this.settings().auto_enable_cross_signing
2154 && let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await
2155 {
2156 error!("Couldn't bootstrap cross signing {e:?}");
2157 }
2158
2159 if let Err(e) = this.backups().setup_and_resume().await {
2160 error!("Couldn't setup and resume backups {e:?}");
2161 }
2162 if let Err(e) = this.recovery().setup().await {
2163 error!("Couldn't setup and resume recovery {e:?}");
2164 }
2165 }
2166 .instrument(Span::current()),
2167 ));
2168
2169 tasks.receive_historic_room_key_bundles = bundle_receiver_task;
2170
2171 self.setup_room_membership_session_discard_handler();
2172 }
2173
2174 pub async fn wait_for_e2ee_initialization_tasks(&self) {
2177 let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
2178
2179 if let Some(task) = task
2180 && let Err(err) = task.await
2181 {
2182 warn!("Error when initializing backups: {err}");
2183 }
2184 }
2185
2186 pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
2196 let olm = self.client.olm_machine().await;
2197 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
2198
2199 if let Some((request_id, request)) = olm.upload_device_keys().await? {
2200 self.client.keys_upload(&request_id, &request).await?;
2201
2202 let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
2203 self.client.keys_query(&request_id, request.device_keys).await?;
2204 }
2205
2206 Ok(())
2207 }
2208
2209 pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
2210 self.recovery().update_state_after_keys_query(response).await;
2211
2212 if let Some(user_id) = self.client.user_id() {
2214 let contains_own_device = response.device_keys.contains_key(user_id);
2215
2216 if contains_own_device {
2217 self.update_verification_state().await;
2218 }
2219 }
2220 }
2221
2222 async fn update_verification_state(&self) {
2223 match self.get_own_device().await {
2224 Ok(device) => {
2225 if let Some(device) = device {
2226 let is_verified = device.is_cross_signed_by_owner();
2227
2228 if is_verified {
2229 self.client.inner.verification_state.set(VerificationState::Verified);
2230 } else {
2231 self.client.inner.verification_state.set(VerificationState::Unverified);
2232 }
2233 } else {
2234 warn!("Couldn't find out own device in the store.");
2235 self.client.inner.verification_state.set(VerificationState::Unknown);
2236 }
2237 }
2238 Err(error) => {
2239 warn!("Failed retrieving own device: {error}");
2240 self.client.inner.verification_state.set(VerificationState::Unknown);
2241 }
2242 }
2243 }
2244
2245 fn setup_room_membership_session_discard_handler(&self) {
2265 let client = WeakClient::from_client(&self.client);
2266 self.client.add_event_handler(|ev: OriginalSyncRoomMemberEvent, room: Room| async move {
2267 let Some(client) = client.get() else {
2268 return;
2270 };
2271 let Some(user_id) = client.user_id() else {
2272 return;
2274 };
2275 let olm = client.olm_machine().await;
2276 let Some(olm) = olm.as_ref() else {
2277 warn!("Cannot discard session - Olm machine is not available");
2278 return;
2279 };
2280
2281 if matches!(
2282 ev.membership_change(),
2283 MembershipChange::Joined |
2284 MembershipChange::Invited |
2285 MembershipChange::KnockAccepted |
2286 MembershipChange::InvitationAccepted |
2287 MembershipChange::ProfileChanged { .. }
2288 ) || ev.sender == user_id {
2289 return;
2291 }
2292
2293 debug!(room_id = ?room.room_id(), member_id = ?ev.sender, "Discarding session as a user left the room");
2294
2295 if let Err(e) = olm.discard_room_key(room.room_id()).await {
2298 warn!(
2299 room_id = ?room.room_id(),
2300 "Error discarding room key after member leave: {e:?}"
2301 );
2302 }
2303 });
2304 }
2305
2306 #[cfg(feature = "experimental-send-custom-to-device")]
2315 pub async fn encrypt_and_send_raw_to_device(
2316 &self,
2317 recipient_devices: Vec<&Device>,
2318 event_type: &str,
2319 content: Raw<AnyToDeviceEventContent>,
2320 share_strategy: CollectStrategy,
2321 ) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
2322 let users = recipient_devices.iter().map(|device| device.user_id());
2323
2324 self.client.claim_one_time_keys(users).await?;
2328
2329 let olm = self.client.olm_machine().await;
2330 let olm = olm.as_ref().expect("Olm machine wasn't started");
2331
2332 let (requests, withhelds) = olm
2333 .encrypt_content_for_devices(
2334 recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
2335 event_type,
2336 &content
2337 .deserialize_as::<serde_json::Value>()
2338 .expect("Deserialize as Value will always work"),
2339 share_strategy,
2340 )
2341 .await?;
2342
2343 let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
2344
2345 withhelds.iter().for_each(|(d, _)| {
2347 failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
2348 });
2349
2350 for request in requests {
2352 let ruma_request = RumaToDeviceRequest::new_raw(
2353 request.event_type.clone(),
2354 request.txn_id.clone(),
2355 request.messages.clone(),
2356 );
2357
2358 let send_result = self
2359 .client
2360 .send_inner(ruma_request, Some(RequestConfig::short_retry()), Default::default())
2361 .await;
2362
2363 if send_result.is_err() {
2365 for (user_id, device_map) in request.messages {
2367 for device_id in device_map.keys() {
2368 match device_id {
2369 DeviceIdOrAllDevices::DeviceId(device_id) => {
2370 failures.push((user_id.clone(), device_id.to_owned()));
2371 }
2372 DeviceIdOrAllDevices::AllDevices => {
2373 }
2375 }
2376 }
2377 }
2378 }
2379 }
2380
2381 Ok(failures)
2382 }
2383}
2384
2385#[cfg(all(test, not(target_family = "wasm")))]
2386mod tests {
2387 use std::{
2388 ops::Not,
2389 str::FromStr,
2390 sync::{
2391 Arc,
2392 atomic::{AtomicBool, Ordering},
2393 },
2394 };
2395
2396 use matrix_sdk_test::{
2397 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
2398 event_factory::EventFactory,
2399 };
2400 use ruma::{
2401 event_id,
2402 events::{reaction::ReactionEventContent, relation::Annotation},
2403 user_id,
2404 };
2405 use serde_json::json;
2406 use wiremock::{
2407 Mock, MockServer, Request, ResponseTemplate,
2408 matchers::{header, method, path_regex},
2409 };
2410
2411 use crate::{
2412 Client, assert_next_matches_with_timeout,
2413 config::RequestConfig,
2414 encryption::{
2415 DuplicateOneTimeKeyErrorMessage, OAuthCrossSigningResetInfo, VerificationState,
2416 },
2417 test_utils::{
2418 client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
2419 },
2420 };
2421
2422 #[async_test]
2423 async fn test_reaction_sending() {
2424 let server = MockServer::start().await;
2425 let client = logged_in_client(Some(server.uri())).await;
2426
2427 let event_id = event_id!("$2:example.org");
2428
2429 Mock::given(method("GET"))
2430 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
2431 .and(header("authorization", "Bearer 1234"))
2432 .respond_with(
2433 ResponseTemplate::new(200)
2434 .set_body_json(EventFactory::new().room_encryption().into_content()),
2435 )
2436 .mount(&server)
2437 .await;
2438
2439 Mock::given(method("PUT"))
2440 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
2441 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2442 "event_id": event_id,
2443 })))
2444 .mount(&server)
2445 .await;
2446
2447 let f = EventFactory::new().sender(user_id!("@example:localhost"));
2448 let response = SyncResponseBuilder::default()
2449 .add_joined_room(
2450 JoinedRoomBuilder::default()
2451 .add_state_event(
2452 f.member(user_id!("@example:localhost")).display_name("example"),
2453 )
2454 .add_state_event(f.default_power_levels())
2455 .add_state_event(f.room_encryption()),
2456 )
2457 .build_sync_response();
2458
2459 client.base_client().receive_sync_response(response).await.unwrap();
2460
2461 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
2462 assert!(
2463 room.latest_encryption_state().await.expect("Getting encryption state").is_encrypted()
2464 );
2465
2466 let event_id = event_id!("$1:example.org");
2467 let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
2468 room.send(reaction).await.expect("Sending the reaction should not fail");
2469
2470 room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
2471 }
2472
2473 #[cfg(feature = "sqlite")]
2474 #[async_test]
2475 async fn test_generation_counter_invalidates_olm_machine() {
2476 use matrix_sdk_base::store::RoomLoadSettings;
2479 let tmp_dir = tempfile::tempdir().unwrap();
2480 let sqlite_path = tmp_dir.path().join("generation_counter_sqlite.db");
2481 let session = mock_matrix_session();
2482
2483 let client1 = Client::builder()
2484 .homeserver_url("http://localhost:1234")
2485 .request_config(RequestConfig::new().disable_retry())
2486 .sqlite_store(&sqlite_path, None)
2487 .build()
2488 .await
2489 .unwrap();
2490 client1
2491 .matrix_auth()
2492 .restore_session(session.clone(), RoomLoadSettings::default())
2493 .await
2494 .unwrap();
2495
2496 let client2 = Client::builder()
2497 .homeserver_url("http://localhost:1234")
2498 .request_config(RequestConfig::new().disable_retry())
2499 .sqlite_store(sqlite_path, None)
2500 .build()
2501 .await
2502 .unwrap();
2503 client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
2504
2505 let guard = client1.encryption().try_lock_store_once().await.unwrap();
2507 assert!(guard.is_none());
2508
2509 client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2510 client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
2511
2512 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2514 assert!(acquired1.is_some());
2515
2516 let initial_olm_machine =
2518 client1.olm_machine().await.clone().expect("must have an olm machine");
2519
2520 let decryption_key = matrix_sdk_base::crypto::store::types::BackupDecryptionKey::new();
2522 let backup_key = decryption_key.megolm_v1_public_key();
2523 backup_key.set_version("1".to_owned());
2524 initial_olm_machine
2525 .backup_machine()
2526 .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
2527 .await
2528 .expect("Should save");
2529
2530 initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
2531
2532 assert!(client1.encryption().backups().are_enabled().await);
2533
2534 let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2536 assert!(acquired2.is_none());
2537
2538 drop(acquired1);
2540
2541 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2543 assert!(acquired1.is_some());
2544
2545 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2547 assert!(initial_olm_machine.same_as(&olm_machine));
2548
2549 drop(acquired1);
2551
2552 let acquired2 = client2.encryption().spin_lock_store(None).await.unwrap();
2554 assert!(acquired2.is_some());
2555
2556 drop(acquired2);
2558
2559 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2561 assert!(acquired1.is_some());
2562
2563 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2565
2566 assert!(!initial_olm_machine.same_as(&olm_machine));
2567
2568 let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
2569 assert!(backup_key_new.decryption_key.is_some());
2570 assert_eq!(
2571 backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
2572 backup_key.to_base64()
2573 );
2574 assert!(client1.encryption().backups().are_enabled().await);
2575 }
2576
2577 #[cfg(feature = "sqlite")]
2578 #[async_test]
2579 async fn test_generation_counter_no_spurious_invalidation() {
2580 use matrix_sdk_base::store::RoomLoadSettings;
2583 let tmp_dir = tempfile::tempdir().unwrap();
2584 let sqlite_path = tmp_dir.path().join("generation_counter_no_spurious_invalidations.db");
2585 let session = mock_matrix_session();
2586
2587 let client = Client::builder()
2588 .homeserver_url("http://localhost:1234")
2589 .request_config(RequestConfig::new().disable_retry())
2590 .sqlite_store(&sqlite_path, None)
2591 .build()
2592 .await
2593 .unwrap();
2594 client
2595 .matrix_auth()
2596 .restore_session(session.clone(), RoomLoadSettings::default())
2597 .await
2598 .unwrap();
2599
2600 let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2601
2602 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2603
2604 let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2606 assert!(initial_olm_machine.same_as(&after_enabling_lock));
2607
2608 {
2609 let client2 = Client::builder()
2611 .homeserver_url("http://localhost:1234")
2612 .request_config(RequestConfig::new().disable_retry())
2613 .sqlite_store(sqlite_path, None)
2614 .build()
2615 .await
2616 .unwrap();
2617 client2
2618 .matrix_auth()
2619 .restore_session(session, RoomLoadSettings::default())
2620 .await
2621 .unwrap();
2622
2623 client2
2624 .encryption()
2625 .enable_cross_process_store_lock("client2".to_owned())
2626 .await
2627 .unwrap();
2628
2629 let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2630 assert!(guard.is_some());
2631
2632 drop(guard);
2633 }
2634
2635 {
2636 let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2637 assert!(acquired.is_some());
2638 }
2639
2640 let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2642 assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2643
2644 {
2645 let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2646 assert!(acquired.is_some());
2647 }
2648
2649 let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2651 assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2652 }
2653
2654 #[async_test]
2655 async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2656 let client = no_retry_test_client(None).await;
2658 let server = MockServer::start().await;
2659
2660 let mut verification_state = client.encryption().verification_state();
2662
2663 assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2665
2666 let keys_requested = Arc::new(AtomicBool::new(false));
2669 let inner_bool = keys_requested.clone();
2670
2671 Mock::given(method("GET"))
2672 .and(path_regex(
2673 r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2674 ))
2675 .respond_with(move |_req: &Request| {
2676 inner_bool.fetch_or(true, Ordering::SeqCst);
2677 ResponseTemplate::new(200).set_body_json(json!({}))
2678 })
2679 .mount(&server)
2680 .await;
2681
2682 set_client_session(&client).await;
2684
2685 assert!(keys_requested.load(Ordering::SeqCst).not());
2687 assert_next_matches_with_timeout!(verification_state, VerificationState::Unverified);
2688 }
2689
2690 #[test]
2691 fn test_oauth_reset_info_from_uiaa_info() {
2692 let auth_info = json!({
2693 "session": "dummy",
2694 "flows": [
2695 {
2696 "stages": [
2697 "org.matrix.cross_signing_reset"
2698 ]
2699 }
2700 ],
2701 "params": {
2702 "org.matrix.cross_signing_reset": {
2703 "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2704 }
2705 },
2706 "msg": "To reset..."
2707 });
2708
2709 let auth_info = serde_json::from_value(auth_info)
2710 .expect("We should be able to deserialize the UiaaInfo");
2711 OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2712 .expect("We should be able to fetch the cross-signing reset info from the auth info");
2713 }
2714
2715 #[test]
2716 fn test_duplicate_one_time_key_error_parsing() {
2717 let message = concat!(
2718 r#"One time key signed_curve25519:AAAAAAAAAAA already exists. "#,
2719 r#"Old key: {"key":"dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs","#,
2720 r#""signatures":{"@example:matrix.org":{"ed25519:AAAAAAAAAA":""#,
2721 r#"Fk45zHAbrd+1j9wZXLjL2Y/+DU/Mnz9yuvlfYBOOT7qExN2Jdud+5BAuNs8nZ/caS4wTF39Kg3zQpzaGERoCBg"}}};"#,
2722 r#" new key: {'key': 'CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno', "#,
2723 r#"'signatures': {'@example:matrix.org': {'ed25519:AAAAAAAAAA': "#,
2724 r#"'BQ9Gp0p+6srF+c8OyruqKKd9R4yaub3THYAyyBB/7X/rG8BwcAqFynzl1aGyFYun4Q+087a5OSiglCXI+/kQAA'}}}"#
2725 );
2726 let message = DuplicateOneTimeKeyErrorMessage::from_str(message)
2727 .expect("We should be able to parse the error message");
2728
2729 assert_eq!(message.old_key.to_base64(), "dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs");
2730 assert_eq!(message.new_key.to_base64(), "CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno");
2731
2732 DuplicateOneTimeKeyErrorMessage::from_str("One time key already exists.")
2733 .expect_err("We shouldn't be able to parse an incomplete error message");
2734 }
2735
2736 fn devices_to_verify_against_keys_query_response(
2740 devices: Vec<serde_json::Value>,
2741 ) -> serde_json::Value {
2742 let device_keys: serde_json::Map<String, serde_json::Value> = devices
2743 .into_iter()
2744 .map(|device| (device.get("device_id").unwrap().as_str().unwrap().to_owned(), device))
2745 .collect();
2746 json!({
2747 "device_keys": {
2748 "@example:localhost": device_keys,
2749 },
2750 "master_keys": {
2751 "@example:localhost": {
2752 "keys": {
2753 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU",
2754 },
2755 "usage": ["master"],
2756 "user_id": "@example:localhost",
2757 },
2758 },
2759 "self_signing_keys": {
2760 "@example:localhost": {
2761 "keys": {
2762 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM",
2763 },
2764 "usage": ["self_signing"],
2765 "user_id": "@example:localhost",
2766 "signatures": {
2767 "@example:localhost": {
2768 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "etO1bB+rCk+TQ/FcjQ8eWu/RsRNQNNQ1Ek+PD6//j8yz6igRjfvuHZaMvr/quAFrirfgExph2TdOwlDgN5bFCQ",
2769 },
2770 },
2771 },
2772 },
2773 "user_signing_keys": {
2774 "@example:localhost": {
2775 "keys": {
2776 "ed25519:CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0": "CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0",
2777 },
2778 "usage": ["user_signing"],
2779 "user_id": "@example:localhost",
2780 "signatures": {
2781 "@example:localhost": {
2782 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "E/DFi/hQTIb/7eSB+HbCXeTLFaLjqWHzLO9GwjL1qdhfO7ew4p6YdtXSH3T2YYr1dKCPteH/4nMYVwOhww2CBg",
2783 },
2784 },
2785 },
2786 }
2787 })
2788 }
2789
2790 #[async_test]
2793 async fn test_devices_to_verify_against_no_devices() {
2796 let server = MockServer::start().await;
2797 let client = logged_in_client(Some(server.uri())).await;
2798
2799 Mock::given(method("POST"))
2800 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2801 .respond_with(
2802 ResponseTemplate::new(200)
2803 .set_body_json(devices_to_verify_against_keys_query_response(vec![])),
2804 )
2805 .mount(&server)
2806 .await;
2807
2808 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2809 }
2810
2811 #[async_test]
2812 async fn test_devices_to_verify_against_cross_signed() {
2815 let server = MockServer::start().await;
2816 let client = logged_in_client(Some(server.uri())).await;
2817
2818 Mock::given(method("POST"))
2819 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2820 .respond_with(ResponseTemplate::new(200).set_body_json(
2821 devices_to_verify_against_keys_query_response(vec![
2822 json!({
2823 "algorithms": [
2824 "m.olm.v1.curve25519-aes-sha2",
2825 "m.megolm.v1.aes-sha2",
2826 ],
2827 "user_id": "@example:localhost",
2828 "device_id": "SIGNEDDEVICE",
2829 "keys": {
2830 "curve25519:SIGNEDDEVICE": "o1LqUtH/sqd3WF+BB2Qr77uw3sDmZhMOz68/IV9aHxs",
2831 "ed25519:SIGNEDDEVICE": "iVoEfMOoUqxXVMLdpZCOgvQuCrT3/kQWkBmB3Phi/lo",
2832 },
2833 "signatures": {
2834 "@example:localhost": {
2835 "ed25519:SIGNEDDEVICE": "C7yRu1fNrdD2EobVdtANMqk3LBtWtTRWrIU22xVS8/Om1kmA/luzek64R3N6JsZhYczVmZYBKhUC9kRvHHwOBg",
2836 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "frfh2HP28GclmGvwTic00Fj4nZCvm4RlRA6U56mnD5920hOi04+L055ojzp6ybZXvC/GQYfyTHwQXlUN1nvxBA",
2837 },
2838 },
2839 })
2840 ])
2841 ))
2842 .mount(&server)
2843 .await;
2844
2845 assert!(client.encryption().has_devices_to_verify_against().await.unwrap());
2846 }
2847
2848 #[async_test]
2849 async fn test_devices_to_verify_against_dehydrated_and_unsigned() {
2852 let server = MockServer::start().await;
2853 let client = logged_in_client(Some(server.uri())).await;
2854 let user_id = client.user_id().unwrap();
2855 let olm_machine = client.olm_machine().await;
2856 let olm_machine = olm_machine.as_ref().unwrap();
2857
2858 Mock::given(method("POST"))
2859 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2860 .respond_with(ResponseTemplate::new(200).set_body_json(
2861 devices_to_verify_against_keys_query_response(vec![
2862 json!({
2863 "algorithms": [
2864 "m.olm.v1.curve25519-aes-sha2",
2865 "m.megolm.v1.aes-sha2",
2866 ],
2867 "user_id": "@example:localhost",
2868 "device_id": "DEHYDRATEDDEVICE",
2869 "keys": {
2870 "curve25519:DEHYDRATEDDEVICE": "XOn5VguAgokZ3p9mBz2yOB395fn6j75G8jIPcXEWQGY",
2871 "ed25519:DEHYDRATEDDEVICE": "4GG5xmBT7z4rgUgmWNlKZ+ABE3QlGgTorF+luCnKfYI",
2872 },
2873 "dehydrated": true,
2874 "signatures": {
2875 "@example:localhost": {
2876 "ed25519:DEHYDRATEDDEVICE": "+OMasB7nzVlMV+zRDxkh4h8h/Q0bY42P1SPv7X2IURIelT5G+d+AYSmg30N4maphxEDBqt/vI8/lIr71exc3Dg",
2877 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "8DzynAgbYgXX1Md5d4Vw91Zstpoi4dpG7levFeVhi4psCAWuBnV76Qu1s2TGjQQ0CLDXEqcxxuX9X4eUK5TGCg",
2878 },
2879 },
2880 }),
2881 json!({
2882 "algorithms": [
2883 "m.olm.v1.curve25519-aes-sha2",
2884 "m.megolm.v1.aes-sha2",
2885 ],
2886 "user_id": "@example:localhost",
2887 "device_id": "UNSIGNEDDEVICE",
2888 "keys": {
2889 "curve25519:UNSIGNEDDEVICE": "mMby6NpprkHxj+ONfO9Z5lBqVUHJBMkrPFSNJhogBkg",
2890 "ed25519:UNSIGNEDDEVICE": "Zifq39ZDrlIaSRf0Hh22owEqXCPE+1JSSgs6LDlubwQ",
2891 },
2892 "signatures": {
2893 "@example:localhost": {
2894 "ed25519:UNSIGNEDDEVICE": "+L29RoDKoTufPGm/Bae65KHno7Z1H7GYhxSKpB4RQZRS7NrR29AMW1PVhEsIozYuDVEFuMZ0L8H3dlcaHxagBA",
2895 },
2896 },
2897 }),
2898 ])
2899 ))
2900 .mount(&server)
2901 .await;
2902
2903 let (request_id, request) = olm_machine.query_keys_for_users([user_id]);
2904 client.keys_query(&request_id, request.device_keys).await.unwrap();
2905
2906 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2907 }
2908
2909 #[cfg(feature = "experimental-send-custom-to-device")]
2910 mod resolve_recipient_devices {
2911 use matrix_sdk_test::async_test;
2912 use ruma::{
2913 OwnedDeviceId, device_id, owned_device_id, to_device::DeviceIdOrAllDevices, user_id,
2914 };
2915
2916 use super::super::Device;
2917 use crate::{Client, test_utils::mocks::MatrixMockServer};
2918
2919 const BOB_FIRST_DEVICE: &str = "B0B0B0B0B";
2920 const BOB_SECOND_DEVICE: &str = "B0B2B0B2";
2921 const BOB_THIRD_DEVICE: &str = "B0B3B0B3";
2922
2923 async fn alice_and_bob_with_two_bob_devices() -> (MatrixMockServer, Client, Client) {
2929 let server = MatrixMockServer::new().await;
2930 server.mock_crypto_endpoints_preset().await;
2931
2932 let (alice, bob) = server.set_up_alice_and_bob_for_encryption().await;
2933 assert_eq!(bob.device_id().unwrap(), device_id!(BOB_FIRST_DEVICE));
2934
2935 server
2936 .set_up_new_device_for_encryption(&bob, device_id!(BOB_SECOND_DEVICE), vec![&alice])
2937 .await;
2938
2939 server
2941 .mock_sync()
2942 .ok_and_run(&alice, |builder| {
2943 builder.add_change_device(bob.user_id().unwrap());
2944 })
2945 .await;
2946
2947 (server, alice, bob)
2948 }
2949
2950 fn device_ids(devices: Vec<Device>) -> Vec<OwnedDeviceId> {
2953 let mut device_ids: Vec<_> =
2954 devices.into_iter().map(|d| d.device_id().to_owned()).collect();
2955 device_ids.sort();
2956 device_ids
2957 }
2958
2959 #[async_test]
2960 async fn test_all_devices_resolves_to_every_known_device() {
2961 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
2962
2963 let (devices, unknown_devices) = alice
2964 .resolve_recipient_devices(
2965 bob.user_id().unwrap(),
2966 vec![DeviceIdOrAllDevices::AllDevices],
2967 )
2968 .await
2969 .unwrap();
2970
2971 assert_eq!(
2972 device_ids(devices),
2973 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
2974 );
2975 assert!(unknown_devices.is_empty(), "`AllDevices` can't reference an unknown device");
2976 }
2977
2978 #[async_test]
2979 async fn test_explicit_device_ids_are_filtered_to_the_requested_ones() {
2980 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
2981
2982 let (devices, unknown_devices) = alice
2983 .resolve_recipient_devices(
2984 bob.user_id().unwrap(),
2985 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_SECOND_DEVICE))],
2986 )
2987 .await
2988 .unwrap();
2989
2990 assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_SECOND_DEVICE)]);
2991 assert!(unknown_devices.is_empty());
2992 }
2993
2994 #[async_test]
2996 async fn test_several_explicit_device_ids_are_all_resolved() {
2997 let (server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
2998
2999 server
3000 .set_up_new_device_for_encryption(&bob, device_id!(BOB_THIRD_DEVICE), vec![&alice])
3001 .await;
3002
3003 server
3005 .mock_sync()
3006 .ok_and_run(&alice, |builder| {
3007 builder.add_change_device(bob.user_id().unwrap());
3008 })
3009 .await;
3010
3011 let (devices, unknown_devices) = alice
3012 .resolve_recipient_devices(
3013 bob.user_id().unwrap(),
3014 vec![
3015 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3016 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_THIRD_DEVICE)),
3017 ],
3018 )
3019 .await
3020 .unwrap();
3021
3022 assert_eq!(
3023 device_ids(devices),
3024 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_THIRD_DEVICE)],
3025 "the second device was not requested and must be left out"
3026 );
3027 assert!(unknown_devices.is_empty());
3028 }
3029
3030 #[async_test]
3031 async fn test_unknown_device_is_reported_back() {
3032 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3033
3034 let (devices, unknown_devices) = alice
3035 .resolve_recipient_devices(
3036 bob.user_id().unwrap(),
3037 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE"))],
3038 )
3039 .await
3040 .unwrap();
3041
3042 assert!(devices.is_empty());
3043 assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3044 }
3045
3046 #[async_test]
3049 async fn test_known_and_unknown_devices_are_split() {
3050 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3051
3052 let (devices, unknown_devices) = alice
3053 .resolve_recipient_devices(
3054 bob.user_id().unwrap(),
3055 vec![
3056 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3057 DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3058 ],
3059 )
3060 .await
3061 .unwrap();
3062
3063 assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3064 assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3065 }
3066
3067 #[async_test]
3071 async fn test_all_devices_takes_precedence_over_explicit_device_ids() {
3072 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3073
3074 let (devices, unknown_devices) = alice
3075 .resolve_recipient_devices(
3076 bob.user_id().unwrap(),
3077 vec![
3078 DeviceIdOrAllDevices::AllDevices,
3079 DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3080 ],
3081 )
3082 .await
3083 .unwrap();
3084
3085 assert_eq!(
3086 device_ids(devices),
3087 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
3088 );
3089 assert!(unknown_devices.is_empty());
3090 }
3091
3092 #[async_test]
3095 async fn test_empty_recipient_list_resolves_to_no_device() {
3096 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3097
3098 let (devices, unknown_devices) =
3099 alice.resolve_recipient_devices(bob.user_id().unwrap(), vec![]).await.unwrap();
3100
3101 assert!(devices.is_empty());
3102 assert!(unknown_devices.is_empty());
3103 }
3104
3105 #[async_test]
3108 async fn test_unknown_user_has_no_device() {
3109 let (_server, alice, _bob) = alice_and_bob_with_two_bob_devices().await;
3110 let unknown_user_id = user_id!("@carol:example.org");
3111
3112 let (devices, unknown_devices) = alice
3113 .resolve_recipient_devices(
3114 unknown_user_id,
3115 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE))],
3116 )
3117 .await
3118 .unwrap();
3119
3120 assert!(devices.is_empty());
3121 assert_eq!(unknown_devices, vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3122
3123 let (devices, unknown_devices) = alice
3126 .resolve_recipient_devices(unknown_user_id, vec![DeviceIdOrAllDevices::AllDevices])
3127 .await
3128 .unwrap();
3129
3130 assert!(devices.is_empty());
3131 assert!(unknown_devices.is_empty());
3132 }
3133 }
3134}