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, OAuth as OAuthData, 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: 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 = Some(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(UiaaCrossSigningResetInfo),
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(
448 UiaaCrossSigningResetInfo::from_auth_info(auth_info)?,
449 )))
450 }
451 } else {
452 Ok(None)
453 }
454 }
455}
456
457#[derive(Debug, Clone)]
460pub struct UiaaCrossSigningResetInfo {
461 pub session: String,
463}
464
465impl UiaaCrossSigningResetInfo {
466 fn from_auth_info(auth_info: &UiaaInfo) -> Result<Self> {
467 Ok(UiaaCrossSigningResetInfo {
468 session: auth_info
469 .session
470 .as_ref()
471 .ok_or_else(|| serde_json::Error::custom("UIAA info is missing a session token"))?
472 .to_owned(),
473 })
474 }
475}
476
477#[derive(Debug, Clone, Deserialize)]
480pub struct OAuthCrossSigningResetInfo {
481 pub approval_url: Url,
483
484 pub session: String,
486}
487
488impl OAuthCrossSigningResetInfo {
489 fn from_auth_info(auth_info: &UiaaInfo) -> Result<Option<Self>> {
490 let Some(parameters) = auth_info.params::<OAuthParams>(&AuthType::OAuth)? else {
491 return Ok(None);
492 };
493
494 Ok(Some(OAuthCrossSigningResetInfo {
495 approval_url: parameters.url.as_str().try_into()?,
496 session: auth_info
497 .session
498 .as_ref()
499 .ok_or_else(|| serde_json::Error::custom("UIAA info is missing a session token"))?
500 .to_owned(),
501 }))
502 }
503
504 pub fn as_auth_data(&self) -> AuthData {
506 AuthData::OAuth(assign!(OAuthData::new(), { session: Some(self.session.clone()) }))
507 }
508}
509
510#[derive(Clone, Debug)]
513pub struct DuplicateOneTimeKeyErrorMessage {
514 pub old_key: Curve25519PublicKey,
516 pub new_key: Curve25519PublicKey,
518}
519
520impl FromStr for DuplicateOneTimeKeyErrorMessage {
521 type Err = serde_json::Error;
522
523 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
524 let mut split = s.split_terminator(';');
528
529 let old_key = split
530 .next()
531 .ok_or(serde_json::Error::custom("Old key is missing in the error message"))?;
532 let new_key = split
533 .next()
534 .ok_or(serde_json::Error::custom("New key is missing in the error message"))?;
535
536 let old_key_index = old_key
539 .find("Old key:")
540 .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
541
542 let old_key = old_key[old_key_index..]
543 .trim()
544 .strip_prefix("Old key:")
545 .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
546
547 let new_key = new_key
550 .trim()
551 .strip_prefix("new key:")
552 .ok_or(serde_json::Error::custom("New key is missing the prefix"))?;
553
554 let new_key = new_key.replace("'", "\"");
557
558 let old_key: SignedKey = serde_json::from_str(old_key)?;
560 let new_key: SignedKey = serde_json::from_str(&new_key)?;
561
562 let old_key = old_key.key();
564 let new_key = new_key.key();
565
566 Ok(Self { old_key, new_key })
567 }
568}
569
570impl Client {
571 pub(crate) async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
572 self.base_client().olm_machine().await
573 }
574
575 pub(crate) async fn mark_request_as_sent(
576 &self,
577 request_id: &TransactionId,
578 response: impl Into<matrix_sdk_base::crypto::types::requests::AnyIncomingResponse<'_>>,
579 ) -> Result<(), matrix_sdk_base::Error> {
580 Ok(self
581 .olm_machine()
582 .await
583 .as_ref()
584 .expect(
585 "We should have an olm machine once we try to mark E2EE related requests as sent",
586 )
587 .mark_request_as_sent(request_id, response)
588 .await?)
589 }
590
591 #[instrument(skip(self, device_keys))]
597 pub(crate) async fn keys_query(
598 &self,
599 request_id: &TransactionId,
600 device_keys: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
601 ) -> Result<get_keys::v3::Response> {
602 let request = assign!(get_keys::v3::Request::new(), { device_keys });
603
604 let response = self.send(request).await?;
605 self.mark_request_as_sent(request_id, &response).await?;
606 self.encryption().update_state_after_keys_query(&response).await;
607
608 Ok(response)
609 }
610
611 pub fn upload_encrypted_file<'a, R: Read + ?Sized + 'a>(
645 &'a self,
646 reader: &'a mut R,
647 ) -> UploadEncryptedFile<'a, R> {
648 UploadEncryptedFile::new(self, reader)
649 }
650
651 pub(crate) async fn upload_encrypted_media_and_thumbnail(
654 &self,
655 data: &[u8],
656 thumbnail: Option<Thumbnail>,
657 send_progress: SharedObservable<TransmissionProgress>,
658 ) -> Result<(MediaSource, Option<(MediaSource, Box<ThumbnailInfo>)>)> {
659 let upload_thumbnail = self.upload_encrypted_thumbnail(thumbnail, send_progress.clone());
660
661 let upload_attachment = async {
662 let mut cursor = Cursor::new(data);
663 self.upload_encrypted_file(&mut cursor)
664 .with_send_progress_observable(send_progress)
665 .await
666 };
667
668 let (thumbnail, file) = try_join(upload_thumbnail, upload_attachment).await?;
669
670 Ok((MediaSource::Encrypted(Box::new(file)), thumbnail))
671 }
672
673 async fn upload_encrypted_thumbnail(
676 &self,
677 thumbnail: Option<Thumbnail>,
678 send_progress: SharedObservable<TransmissionProgress>,
679 ) -> Result<Option<(MediaSource, Box<ThumbnailInfo>)>> {
680 let Some(thumbnail) = thumbnail else {
681 return Ok(None);
682 };
683
684 let (data, _, thumbnail_info) = thumbnail.into_parts();
685 let mut cursor = Cursor::new(data);
686
687 let file = self
688 .upload_encrypted_file(&mut cursor)
689 .with_send_progress_observable(send_progress)
690 .await?;
691
692 Ok(Some((MediaSource::Encrypted(Box::new(file)), thumbnail_info)))
693 }
694
695 pub(crate) async fn claim_one_time_keys(
701 &self,
702 users: impl Iterator<Item = &UserId>,
703 ) -> Result<()> {
704 let _lock = self.locks().key_claim_lock.lock().await;
705
706 if let Some((request_id, request)) = self
707 .olm_machine()
708 .await
709 .as_ref()
710 .ok_or(Error::NoOlmMachine)?
711 .get_missing_sessions(users)
712 .await?
713 {
714 let response = self.send(request).await?;
715 self.mark_request_as_sent(&request_id, &response).await?;
716 }
717
718 Ok(())
719 }
720
721 #[instrument(skip(self, request))]
731 pub(crate) async fn keys_upload(
732 &self,
733 request_id: &TransactionId,
734 request: &upload_keys::v3::Request,
735 ) -> Result<upload_keys::v3::Response> {
736 debug!(
737 device_keys = request.device_keys.is_some(),
738 one_time_key_count = request.one_time_keys.len(),
739 "Uploading public encryption keys",
740 );
741
742 let response = self.send(request.clone()).await?;
743 self.mark_request_as_sent(request_id, &response).await?;
744
745 Ok(response)
746 }
747
748 pub(crate) async fn room_send_helper(
749 &self,
750 request: &RoomMessageRequest,
751 ) -> Result<send_message_event::v3::Response> {
752 let content = request.content.clone();
753 let txn_id = request.txn_id.clone();
754 let room_id = &request.room_id;
755
756 self.get_room(room_id)
757 .expect("Can't send a message to a room that isn't known to the store")
758 .send(*content)
759 .with_transaction_id(txn_id)
760 .await
761 .map(|result| result.response)
762 }
763
764 pub(crate) async fn send_to_device(
765 &self,
766 request: &ToDeviceRequest,
767 ) -> HttpResult<ToDeviceResponse> {
768 let request = RumaToDeviceRequest::new_raw(
769 request.event_type.clone(),
770 request.txn_id.clone(),
771 request.messages.clone(),
772 );
773
774 self.send(request).await
775 }
776
777 pub(crate) async fn send_verification_request(
778 &self,
779 request: OutgoingVerificationRequest,
780 ) -> Result<()> {
781 use matrix_sdk_base::crypto::types::requests::OutgoingVerificationRequest::*;
782
783 match request {
784 ToDevice(t) => {
785 self.send_to_device(&t).await?;
786 }
787 InRoom(r) => {
788 self.room_send_helper(&r).await?;
789 }
790 }
791
792 Ok(())
793 }
794
795 async fn send_outgoing_request(&self, r: OutgoingRequest) -> Result<()> {
796 use matrix_sdk_base::crypto::types::requests::AnyOutgoingRequest;
797
798 match r.request() {
799 AnyOutgoingRequest::KeysQuery(request) => {
800 self.keys_query(r.request_id(), request.device_keys.clone()).await?;
801 }
802 AnyOutgoingRequest::KeysUpload(request) => {
803 let response = self.keys_upload(r.request_id(), request).await;
804
805 if let Err(e) = &response {
806 match e.as_client_api_error() {
807 Some(e) if e.status_code == 400 => {
808 if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &e.body
809 {
810 {
816 let already_reported = self
817 .state_store()
818 .get_kv_data(StateStoreDataKey::OneTimeKeyAlreadyUploaded)
819 .await?
820 .is_some();
821
822 if message.starts_with("One time key") && !already_reported {
823 let error_message =
824 DuplicateOneTimeKeyErrorMessage::from_str(message);
825
826 if let Ok(message) = &error_message {
827 error!(
828 sentry = true,
829 old_key = %message.old_key,
830 new_key = %message.new_key,
831 "Duplicate one-time keys have been uploaded"
832 );
833 } else {
834 error!(
835 sentry = true,
836 "Duplicate one-time keys have been uploaded"
837 );
838 }
839
840 self.state_store()
841 .set_kv_data(
842 StateStoreDataKey::OneTimeKeyAlreadyUploaded,
843 StateStoreDataValue::OneTimeKeyAlreadyUploaded,
844 )
845 .await?;
846
847 if let Err(e) = self
848 .inner
849 .duplicate_key_upload_error_sender
850 .send(error_message.ok())
851 {
852 error!(
853 "Failed to dispatch duplicate key upload error notification: {}",
854 e
855 );
856 }
857 }
858 }
859 }
860 }
861 _ => {}
862 }
863
864 response?;
865 }
866 }
867 AnyOutgoingRequest::ToDeviceRequest(request) => {
868 let response = self.send_to_device(request).await?;
869 self.mark_request_as_sent(r.request_id(), &response).await?;
870 }
871 AnyOutgoingRequest::SignatureUpload(request) => {
872 let response = self.send(request.clone()).await?;
873 self.mark_request_as_sent(r.request_id(), &response).await?;
874 }
875 AnyOutgoingRequest::RoomMessage(request) => {
876 let response = self.room_send_helper(request).await?;
877 self.mark_request_as_sent(r.request_id(), &response).await?;
878 }
879 AnyOutgoingRequest::KeysClaim(request) => {
880 let response = self.send(request.clone()).await?;
881 self.mark_request_as_sent(r.request_id(), &response).await?;
882 }
883 }
884
885 Ok(())
886 }
887
888 #[instrument(skip_all)]
889 pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
890 const MAX_CONCURRENT_REQUESTS: usize = 20;
891
892 if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
895 warn!("Error while claiming one-time keys {:?}", e);
896 }
897
898 let outgoing_requests = stream::iter(
899 self.olm_machine()
900 .await
901 .as_ref()
902 .ok_or(Error::NoOlmMachine)?
903 .outgoing_requests()
904 .await?,
905 )
906 .map(|r| self.send_outgoing_request(r));
907
908 let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
909
910 requests
911 .for_each(|r| async move {
912 match r {
913 Ok(_) => (),
914 Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
915 }
916 })
917 .await;
918
919 Ok(())
920 }
921}
922
923#[cfg(feature = "experimental-send-custom-to-device")]
924impl Client {
925 pub async fn send_encrypted_to_device(
953 &self,
954 event_type: &ToDeviceEventType,
955 recipients: BTreeMap<OwnedUserId, Vec<DeviceIdOrAllDevices>>,
956 content: Raw<AnyToDeviceEventContent>,
957 ) -> Result<BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>> {
958 let mut failures: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>> = BTreeMap::new();
959 let mut recipient_devices = Vec::<_>::new();
960
961 for (user_id, recipient_device_ids) in recipients {
963 let (devices, unknown_devices) =
964 self.resolve_recipient_devices(&user_id, recipient_device_ids).await?;
965 recipient_devices.extend(devices);
966 if !unknown_devices.is_empty() {
967 failures.insert(user_id, unknown_devices);
968 }
969 }
970
971 if !recipient_devices.is_empty() {
972 let encrypt_and_send_failures = self
973 .encryption()
974 .encrypt_and_send_raw_to_device(
975 recipient_devices.iter().collect(),
976 &event_type.to_string(),
977 content,
978 CollectStrategy::AllDevices,
979 )
980 .await?;
981
982 for (user_id, device_id) in encrypt_and_send_failures {
983 failures.entry(user_id).or_default().push(device_id)
984 }
985 }
986
987 Ok(failures)
988 }
989
990 async fn resolve_recipient_devices(
999 &self,
1000 user_id: &UserId,
1001 recipient_device_ids: Vec<DeviceIdOrAllDevices>,
1002 ) -> Result<(Vec<Device>, Vec<OwnedDeviceId>)> {
1003 let user_devices = self.encryption().get_user_devices(user_id).await?;
1004
1005 if recipient_device_ids.contains(&DeviceIdOrAllDevices::AllDevices) {
1006 let devices: Vec<_> = user_devices.devices().collect();
1010
1011 if devices.is_empty() {
1012 warn!(
1013 "Recipient list contains `AllDevices` but no devices found for user {user_id}."
1014 );
1015 }
1016 if recipient_device_ids.len() > 1 {
1017 warn!(
1018 "The recipient_device_ids list for {user_id} contains both `AllDevices` and explicit `DeviceId` entries. Only consider `AllDevices`",
1019 );
1020 }
1021
1022 Ok((devices, Vec::new()))
1023 } else {
1024 let (found_device_ids, devices): (BTreeSet<_>, Vec<_>) = user_devices
1027 .devices()
1028 .map(|device| (device.device_id().to_owned(), device))
1029 .filter(|(device_id, _)| {
1030 recipient_device_ids
1031 .contains(&DeviceIdOrAllDevices::DeviceId(device_id.clone()))
1032 })
1033 .unzip();
1034
1035 let requested_device_ids: BTreeSet<_> = recipient_device_ids
1036 .into_iter()
1037 .filter_map(|d| as_variant!(d, DeviceIdOrAllDevices::DeviceId))
1038 .collect();
1039
1040 let missing_devices =
1043 requested_device_ids.difference(&found_device_ids).map(ToOwned::to_owned).collect();
1044
1045 Ok((devices, missing_devices))
1046 }
1047 }
1048}
1049
1050#[cfg(any(feature = "testing", test))]
1051impl Client {
1052 pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
1054 self.olm_machine().await
1055 }
1056
1057 pub fn abort_bundle_receiver_task(&self) {
1059 let tasks = self.inner.e2ee.tasks.lock();
1060 if let Some(task) = tasks.receive_historic_room_key_bundles.as_ref() {
1061 task.abort()
1062 }
1063 }
1064}
1065
1066#[derive(Debug, Clone)]
1070pub struct Encryption {
1071 client: Client,
1073}
1074
1075impl Encryption {
1076 pub(crate) fn new(client: Client) -> Self {
1077 Self { client }
1078 }
1079
1080 pub(crate) fn settings(&self) -> EncryptionSettings {
1082 self.client.inner.e2ee.encryption_settings
1083 }
1084
1085 pub async fn ed25519_key(&self) -> Option<String> {
1088 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
1089 }
1090
1091 pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
1093 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
1094 }
1095
1096 pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
1098 match self.get_own_device().await {
1099 Ok(Some(device)) => device.first_time_seen_ts(),
1100 _ => MilliSecondsSinceUnixEpoch::now(),
1102 }
1103 }
1104
1105 pub async fn import_secrets_bundle(
1118 &self,
1119 bundle: &SecretsBundle,
1120 ) -> Result<(), BundleImportError> {
1121 self.import_secrets_bundle_impl(bundle).await?;
1122
1123 self.ensure_device_keys_upload().await?;
1126 self.wait_for_e2ee_initialization_tasks().await;
1127
1128 if !self.backups().are_enabled().await {
1133 self.backups().maybe_resume_backups().await?;
1134 }
1135
1136 Ok(())
1137 }
1138
1139 pub(crate) async fn import_secrets_bundle_impl(
1140 &self,
1141 bundle: &SecretsBundle,
1142 ) -> Result<(), SecretImportError> {
1143 let olm_machine = self.client.olm_machine().await;
1144 let olm_machine =
1145 olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
1146
1147 olm_machine.store().import_secrets_bundle(bundle).await
1148 }
1149
1150 pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
1155 let olm = self.client.olm_machine().await;
1156 let machine = olm.as_ref()?;
1157 Some(machine.cross_signing_status().await)
1158 }
1159
1160 pub async fn has_devices_to_verify_against(&self) -> Result<bool> {
1166 let olm_machine = self.client.olm_machine().await;
1167 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1168 let user_id = olm_machine.user_id();
1169
1170 self.ensure_initial_key_query().await?;
1171
1172 let devices = self.get_user_devices(user_id).await?;
1173
1174 let ret = devices.devices().any(|device| {
1175 device.is_cross_signed_by_owner()
1176 && device.curve25519_key().is_some()
1177 && !device.is_dehydrated()
1178 });
1179
1180 Ok(ret)
1181 }
1182
1183 pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
1188 if let Some(machine) = self.client.olm_machine().await.as_ref() {
1189 machine.tracked_users().await
1190 } else {
1191 Ok(HashSet::new())
1192 }
1193 }
1194
1195 pub fn verification_state(&self) -> Subscriber<VerificationState> {
1218 self.client.inner.verification_state.subscribe_reset()
1219 }
1220
1221 pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
1223 let olm = self.client.olm_machine().await;
1224 let olm = olm.as_ref()?;
1225 #[allow(clippy::bind_instead_of_map)]
1226 olm.get_verification(user_id, flow_id).and_then(|v| match v {
1227 matrix_sdk_base::crypto::Verification::SasV1(sas) => {
1228 Some(SasVerification { inner: sas, client: self.client.clone() }.into())
1229 }
1230 #[cfg(feature = "qrcode")]
1231 matrix_sdk_base::crypto::Verification::QrV1(qr) => {
1232 Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
1233 }
1234 _ => None,
1235 })
1236 }
1237
1238 pub async fn get_verification_request(
1241 &self,
1242 user_id: &UserId,
1243 flow_id: impl AsRef<str>,
1244 ) -> Option<VerificationRequest> {
1245 let olm = self.client.olm_machine().await;
1246 let olm = olm.as_ref()?;
1247
1248 olm.get_verification_request(user_id, flow_id)
1249 .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
1250 }
1251
1252 pub async fn get_device(
1285 &self,
1286 user_id: &UserId,
1287 device_id: &DeviceId,
1288 ) -> Result<Option<Device>, CryptoStoreError> {
1289 let olm = self.client.olm_machine().await;
1290 let Some(machine) = olm.as_ref() else { return Ok(None) };
1291 let device = machine.get_device(user_id, device_id, None).await?;
1292 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1293 }
1294
1295 pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
1302 let olm = self.client.olm_machine().await;
1303 let Some(machine) = olm.as_ref() else { return Ok(None) };
1304 let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
1305 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1306 }
1307
1308 pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
1334 let devices = self
1335 .client
1336 .olm_machine()
1337 .await
1338 .as_ref()
1339 .ok_or(Error::NoOlmMachine)?
1340 .get_user_devices(user_id, None)
1341 .await?;
1342
1343 Ok(UserDevices { inner: devices, client: self.client.clone() })
1344 }
1345
1346 pub async fn get_user_identity(
1382 &self,
1383 user_id: &UserId,
1384 ) -> Result<Option<UserIdentity>, CryptoStoreError> {
1385 let olm = self.client.olm_machine().await;
1386 let Some(olm) = olm.as_ref() else { return Ok(None) };
1387 let identity = olm.get_identity(user_id, None).await?;
1388
1389 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1390 }
1391
1392 pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
1430 let olm = self.client.olm_machine().await;
1431 let Some(olm) = olm.as_ref() else { return Ok(None) };
1432
1433 let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
1434 self.client.keys_query(&request_id, request.device_keys).await?;
1435
1436 let identity = olm.get_identity(user_id, None).await?;
1437 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1438 }
1439
1440 pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates> + use<>> {
1472 let olm = self.client.olm_machine().await;
1473 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1474 let client = self.client.to_owned();
1475
1476 Ok(olm
1477 .store()
1478 .devices_stream()
1479 .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1480 }
1481
1482 pub async fn user_identities_stream(
1511 &self,
1512 ) -> Result<impl Stream<Item = IdentityUpdates> + use<>> {
1513 let olm = self.client.olm_machine().await;
1514 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1515 let client = self.client.to_owned();
1516
1517 Ok(olm
1518 .store()
1519 .user_identities_stream()
1520 .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1521 }
1522
1523 pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1567 let olm = self.client.olm_machine().await;
1568 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1569
1570 let CrossSigningBootstrapRequests {
1571 upload_signing_keys_req,
1572 upload_keys_req,
1573 upload_signatures_req,
1574 } = olm.bootstrap_cross_signing(false).await?;
1575
1576 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1577 auth: auth_data,
1578 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1579 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1580 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1581 });
1582
1583 if let Some(req) = upload_keys_req {
1584 self.client.send_outgoing_request(req).await?;
1585 }
1586 self.client.send(upload_signing_keys_req).await?;
1587 self.client.send(upload_signatures_req).await?;
1588
1589 Ok(())
1590 }
1591
1592 pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1630 let olm = self.client.olm_machine().await;
1631 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1632
1633 let CrossSigningBootstrapRequests {
1634 upload_keys_req,
1635 upload_signing_keys_req,
1636 upload_signatures_req,
1637 } = olm.bootstrap_cross_signing(true).await?;
1638
1639 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1640 auth: None,
1641 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1642 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1643 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1644 });
1645
1646 if let Some(req) = upload_keys_req {
1647 self.client.send_outgoing_request(req).await?;
1648 }
1649
1650 if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1651 if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1652 let client = self.client.clone();
1653
1654 Ok(Some(CrossSigningResetHandle::new(
1655 client,
1656 upload_signing_keys_req,
1657 upload_signatures_req,
1658 auth_type,
1659 )))
1660 } else {
1661 Err(error.into())
1662 }
1663 } else {
1664 self.client.send(upload_signatures_req).await?;
1665
1666 Ok(None)
1667 }
1668 }
1669
1670 async fn ensure_initial_key_query(&self) -> Result<()> {
1673 let olm_machine = self.client.olm_machine().await;
1674 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1675
1676 let user_id = olm_machine.user_id();
1677
1678 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1679 let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1680 self.client.keys_query(&request_id, request.device_keys).await?;
1681 }
1682
1683 Ok(())
1684 }
1685
1686 pub async fn bootstrap_cross_signing_if_needed(
1735 &self,
1736 auth_data: Option<AuthData>,
1737 ) -> Result<()> {
1738 let olm_machine = self.client.olm_machine().await;
1739 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1740 let user_id = olm_machine.user_id();
1741
1742 self.ensure_initial_key_query().await?;
1743
1744 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1745 self.bootstrap_cross_signing(auth_data).await?;
1746 }
1747
1748 Ok(())
1749 }
1750
1751 #[cfg(not(target_family = "wasm"))]
1802 pub async fn export_room_keys(
1803 &self,
1804 path: PathBuf,
1805 passphrase: &str,
1806 predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1807 ) -> Result<()> {
1808 let olm = self.client.olm_machine().await;
1809 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1810
1811 let keys = olm.store().export_room_keys(predicate).await?;
1812 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1813
1814 let encrypt = move || -> Result<()> {
1815 let export: String =
1816 matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1817 let mut file = std::fs::File::create(path)?;
1818 file.write_all(&export.into_bytes())?;
1819 Ok(())
1820 };
1821
1822 let task = tokio::task::spawn_blocking(encrypt);
1823 task.await.expect("Task join error")
1824 }
1825
1826 #[cfg(not(target_family = "wasm"))]
1863 pub async fn import_room_keys(
1864 &self,
1865 path: PathBuf,
1866 passphrase: &str,
1867 ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1868 let olm = self.client.olm_machine().await;
1869 let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1870 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1871
1872 let decrypt = move || {
1873 let file = std::fs::File::open(path)?;
1874 matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1875 };
1876
1877 let task = tokio::task::spawn_blocking(decrypt);
1878 let import = task.await.expect("Task join error")?;
1879
1880 let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1881
1882 self.backups().maybe_trigger_backup();
1883
1884 Ok(ret)
1885 }
1886
1887 pub async fn room_keys_received_stream(
1918 &self,
1919 ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<>>
1920 {
1921 let olm = self.client.olm_machine().await;
1922 let olm = olm.as_ref()?;
1923
1924 Some(olm.store().room_keys_received_stream())
1925 }
1926
1927 pub async fn historic_room_key_stream(
1959 &self,
1960 ) -> Option<impl Stream<Item = RoomKeyBundleInfo> + use<>> {
1961 let olm = self.client.olm_machine().await;
1962 let olm = olm.as_ref()?;
1963
1964 Some(olm.store().historic_room_key_stream())
1965 }
1966
1967 pub fn secret_storage(&self) -> SecretStorage {
1969 SecretStorage { client: self.client.to_owned() }
1970 }
1971
1972 pub fn backups(&self) -> Backups {
1974 Backups { client: self.client.to_owned() }
1975 }
1976
1977 pub fn recovery(&self) -> Recovery {
1979 Recovery { client: self.client.to_owned() }
1980 }
1981
1982 pub fn dehydrated_devices(&self) -> dehydrated_devices::DehydratedDevices {
1989 dehydrated_devices::DehydratedDevices { client: self.client.to_owned() }
1990 }
1991
1992 pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
2003 if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
2005 let prev_holder = prev_lock.lock_holder();
2006 if prev_holder.is_some() && prev_holder.unwrap() == lock_value {
2007 return Ok(());
2008 }
2009 warn!(
2010 "Recreating cross-process store lock with a different holder value: \
2011 prev was {prev_holder:?}, new is {lock_value}"
2012 );
2013 }
2014
2015 let olm_machine = self.client.base_client().olm_machine().await;
2016 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
2017
2018 let lock = olm_machine.store().create_store_lock(
2019 "cross_process_lock".to_owned(),
2020 CrossProcessLockConfig::multi_process(lock_value.to_owned()),
2021 );
2022
2023 {
2029 let lock_result = lock.try_lock_once().await?;
2030
2031 if lock_result.is_ok() {
2032 olm_machine
2033 .initialize_crypto_store_generation(
2034 &self.client.locks().crypto_store_generation,
2035 )
2036 .await?;
2037 }
2038 }
2039
2040 self.client
2041 .locks()
2042 .cross_process_crypto_store_lock
2043 .set(lock)
2044 .map_err(|_| Error::BadCryptoStoreState)?;
2045
2046 Ok(())
2047 }
2048
2049 #[instrument(skip(self), fields(olm_machine_new_generation, olm_machine_generation))]
2054 async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
2055 let olm_machine_guard = self.client.olm_machine().await;
2056 if let Some(olm_machine) = olm_machine_guard.as_ref() {
2057 let (new_gen, generation_number) = olm_machine
2058 .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
2059 .await?;
2060
2061 Span::current()
2062 .record("olm_machine_new_generation", new_gen)
2063 .record("olm_machine_generation", generation_number);
2064 debug!("OlmMachine generation maintained in CryptoStore");
2065
2066 if new_gen {
2068 drop(olm_machine_guard);
2070 self.client.base_client().regenerate_olm(None).await?;
2072 }
2073 Ok(generation_number)
2074 } else {
2075 warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
2080 Ok(0)
2081 }
2082 }
2083
2084 pub async fn spin_lock_store(
2092 &self,
2093 max_backoff: Option<u32>,
2094 ) -> Result<Option<CrossProcessLockGuard>, Error> {
2095 self.lock_store(async move |lock| lock.spin_lock(max_backoff).await).await
2096 }
2097
2098 pub async fn try_lock_store_once(&self) -> Result<Option<CrossProcessLockGuard>, Error> {
2106 match self.lock_store(CrossProcessLock::try_lock_once).await {
2107 Err(Error::CrossProcessLockError(e))
2108 if matches!(*e, CrossProcessLockError::Unobtained(_)) =>
2109 {
2110 Ok(None)
2111 }
2112 other => other,
2113 }
2114 }
2115
2116 pub async fn lock_store<F: AcquireCrossProcessLockFn<LockableCryptoStore>>(
2123 &self,
2124 acquire: F,
2125 ) -> Result<Option<CrossProcessLockGuard>, Error> {
2126 let wrap_err = |e: CryptoStoreError| {
2127 Error::CrossProcessLockError(Box::new(CrossProcessLockError::TryLock(Arc::new(e))))
2128 };
2129 if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
2130 let guard = acquire(lock).await.map_err(wrap_err)??;
2131 let _ = self.on_lock_newly_acquired().await?;
2132 Ok(Some(guard.into_guard()))
2133 } else {
2134 Ok(None)
2135 }
2136 }
2137
2138 #[cfg(any(test, feature = "testing"))]
2140 pub async fn uploaded_key_count(&self) -> Result<u64> {
2141 let olm_machine = self.client.olm_machine().await;
2142 let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
2143 Ok(olm_machine.uploaded_key_count().await?)
2144 }
2145
2146 pub(crate) async fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
2171 let bundle_receiver_task = if self.client.inner.enable_share_history_on_invite {
2176 Some(BundleReceiverTask::new(&self.client).await)
2177 } else {
2178 None
2179 };
2180
2181 let mut tasks = self.client.inner.e2ee.tasks.lock();
2182
2183 let this = self.clone();
2184
2185 tasks.setup_e2ee = Some(spawn(
2186 async move {
2187 this.update_verification_state().await;
2190
2191 if this.settings().auto_enable_cross_signing
2192 && let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await
2193 {
2194 error!("Couldn't bootstrap cross signing {e:?}");
2195 }
2196
2197 if let Err(e) = this.backups().setup_and_resume().await {
2198 error!("Couldn't setup and resume backups {e:?}");
2199 }
2200 if let Err(e) = this.recovery().setup().await {
2201 error!("Couldn't setup and resume recovery {e:?}");
2202 }
2203 }
2204 .instrument(Span::current()),
2205 ));
2206
2207 tasks.receive_historic_room_key_bundles = bundle_receiver_task;
2208
2209 self.setup_room_membership_session_discard_handler();
2210 }
2211
2212 pub async fn wait_for_e2ee_initialization_tasks(&self) {
2215 let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
2216
2217 if let Some(task) = task
2218 && let Err(err) = task.await
2219 {
2220 warn!("Error when initializing backups: {err}");
2221 }
2222 }
2223
2224 pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
2234 let olm = self.client.olm_machine().await;
2235 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
2236
2237 if let Some((request_id, request)) = olm.upload_device_keys().await? {
2238 self.client.keys_upload(&request_id, &request).await?;
2239
2240 let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
2241 self.client.keys_query(&request_id, request.device_keys).await?;
2242 }
2243
2244 Ok(())
2245 }
2246
2247 pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
2248 self.recovery().update_state_after_keys_query(response).await;
2249
2250 if let Some(user_id) = self.client.user_id() {
2252 let contains_own_device = response.device_keys.contains_key(user_id);
2253
2254 if contains_own_device {
2255 self.update_verification_state().await;
2256 }
2257 }
2258 }
2259
2260 async fn update_verification_state(&self) {
2261 match self.get_own_device().await {
2262 Ok(device) => {
2263 if let Some(device) = device {
2264 let is_verified = device.is_cross_signed_by_owner();
2265
2266 if is_verified {
2267 self.client.inner.verification_state.set(VerificationState::Verified);
2268 } else {
2269 self.client.inner.verification_state.set(VerificationState::Unverified);
2270 }
2271 } else {
2272 warn!("Couldn't find out own device in the store.");
2273 self.client.inner.verification_state.set(VerificationState::Unknown);
2274 }
2275 }
2276 Err(error) => {
2277 warn!("Failed retrieving own device: {error}");
2278 self.client.inner.verification_state.set(VerificationState::Unknown);
2279 }
2280 }
2281 }
2282
2283 fn setup_room_membership_session_discard_handler(&self) {
2303 let client = self.client.downgrade();
2304 self.client.add_event_handler(|ev: OriginalSyncRoomMemberEvent, room: Room| async move {
2305 let Some(client) = client.get() else {
2306 return;
2308 };
2309 let Some(user_id) = client.user_id() else {
2310 return;
2312 };
2313 let olm = client.olm_machine().await;
2314 let Some(olm) = olm.as_ref() else {
2315 warn!("Cannot discard session - Olm machine is not available");
2316 return;
2317 };
2318
2319 if matches!(
2320 ev.membership_change(),
2321 MembershipChange::Joined |
2322 MembershipChange::Invited |
2323 MembershipChange::KnockAccepted |
2324 MembershipChange::InvitationAccepted |
2325 MembershipChange::ProfileChanged { .. }
2326 ) || ev.sender == user_id {
2327 return;
2329 }
2330
2331 debug!(room_id = ?room.room_id(), member_id = ?ev.sender, "Discarding session as a user left the room");
2332
2333 if let Err(e) = olm.discard_room_key(room.room_id()).await {
2337 warn!(
2338 room_id = ?room.room_id(),
2339 "Error discarding room key after member leave: {e:?}"
2340 );
2341 }
2342 });
2343 }
2344
2345 #[cfg(feature = "experimental-send-custom-to-device")]
2355 pub async fn encrypt_and_send_raw_to_device(
2356 &self,
2357 recipient_devices: Vec<&Device>,
2358 event_type: &str,
2359 content: Raw<AnyToDeviceEventContent>,
2360 share_strategy: CollectStrategy,
2361 ) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
2362 let users = recipient_devices.iter().map(|device| device.user_id());
2363
2364 self.client.claim_one_time_keys(users).await?;
2368
2369 let olm = self.client.olm_machine().await;
2370 let olm = olm.as_ref().expect("Olm machine wasn't started");
2371
2372 let (requests, withhelds) = olm
2373 .encrypt_content_for_devices(
2374 recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
2375 event_type,
2376 &content
2377 .deserialize_as::<serde_json::Value>()
2378 .expect("Deserialize as Value will always work"),
2379 share_strategy,
2380 )
2381 .await?;
2382
2383 let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
2384
2385 withhelds.iter().for_each(|(d, _)| {
2387 failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
2388 });
2389
2390 for request in requests {
2392 let ruma_request = RumaToDeviceRequest::new_raw(
2393 request.event_type.clone(),
2394 request.txn_id.clone(),
2395 request.messages.clone(),
2396 );
2397
2398 let send_result = self
2399 .client
2400 .send_inner(ruma_request, Some(RequestConfig::short_retry()), Default::default())
2401 .await;
2402
2403 if send_result.is_err() {
2406 for (user_id, device_map) in request.messages {
2408 for device_id in device_map.keys() {
2409 match device_id {
2410 DeviceIdOrAllDevices::DeviceId(device_id) => {
2411 failures.push((user_id.clone(), device_id.to_owned()));
2412 }
2413 DeviceIdOrAllDevices::AllDevices => {
2414 }
2416 }
2417 }
2418 }
2419 }
2420 }
2421
2422 Ok(failures)
2423 }
2424}
2425
2426#[cfg(all(test, not(target_family = "wasm")))]
2427mod tests {
2428 use std::{
2429 ops::Not,
2430 str::FromStr,
2431 sync::{
2432 Arc,
2433 atomic::{AtomicBool, Ordering},
2434 },
2435 };
2436
2437 use matrix_sdk_test::{
2438 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
2439 event_factory::EventFactory,
2440 };
2441 use ruma::{
2442 event_id,
2443 events::{reaction::ReactionEventContent, relation::Annotation},
2444 user_id,
2445 };
2446 use serde_json::json;
2447 use wiremock::{
2448 Mock, MockServer, Request, ResponseTemplate,
2449 matchers::{header, method, path_regex},
2450 };
2451
2452 use crate::{
2453 Client, assert_next_matches_with_timeout,
2454 config::RequestConfig,
2455 encryption::{
2456 DuplicateOneTimeKeyErrorMessage, OAuthCrossSigningResetInfo, VerificationState,
2457 },
2458 test_utils::{
2459 client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
2460 },
2461 };
2462
2463 #[async_test]
2464 async fn test_reaction_sending() {
2465 let server = MockServer::start().await;
2466 let client = logged_in_client(Some(server.uri())).await;
2467
2468 let event_id = event_id!("$2:example.org");
2469
2470 Mock::given(method("GET"))
2471 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
2472 .and(header("authorization", "Bearer 1234"))
2473 .respond_with(
2474 ResponseTemplate::new(200)
2475 .set_body_json(EventFactory::new().room_encryption().into_content()),
2476 )
2477 .mount(&server)
2478 .await;
2479
2480 Mock::given(method("PUT"))
2481 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
2482 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2483 "event_id": event_id,
2484 })))
2485 .mount(&server)
2486 .await;
2487
2488 let f = EventFactory::new().sender(user_id!("@example:localhost"));
2489 let response = SyncResponseBuilder::default()
2490 .add_joined_room(
2491 JoinedRoomBuilder::default()
2492 .add_state_event(
2493 f.member(user_id!("@example:localhost")).display_name("example"),
2494 )
2495 .add_state_event(f.default_power_levels())
2496 .add_state_event(f.room_encryption()),
2497 )
2498 .build_sync_response();
2499
2500 client.base_client().receive_sync_response(response).await.unwrap();
2501
2502 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
2503 assert!(
2504 room.latest_encryption_state().await.expect("Getting encryption state").is_encrypted()
2505 );
2506
2507 let event_id = event_id!("$1:example.org");
2508 let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
2509 room.send(reaction).await.expect("Sending the reaction should not fail");
2510
2511 room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
2512 }
2513
2514 #[cfg(feature = "sqlite")]
2515 #[async_test]
2516 async fn test_generation_counter_invalidates_olm_machine() {
2517 use matrix_sdk_base::store::RoomLoadSettings;
2520 let tmp_dir = tempfile::tempdir().unwrap();
2521 let sqlite_path = tmp_dir.path().join("generation_counter_sqlite.db");
2522 let session = mock_matrix_session();
2523
2524 let client1 = Client::builder()
2525 .homeserver_url("http://localhost:1234")
2526 .request_config(RequestConfig::new().disable_retry())
2527 .sqlite_store(&sqlite_path, None)
2528 .build()
2529 .await
2530 .unwrap();
2531 client1
2532 .matrix_auth()
2533 .restore_session(session.clone(), RoomLoadSettings::default())
2534 .await
2535 .unwrap();
2536
2537 let client2 = Client::builder()
2538 .homeserver_url("http://localhost:1234")
2539 .request_config(RequestConfig::new().disable_retry())
2540 .sqlite_store(sqlite_path, None)
2541 .build()
2542 .await
2543 .unwrap();
2544 client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
2545
2546 let guard = client1.encryption().try_lock_store_once().await.unwrap();
2549 assert!(guard.is_none());
2550
2551 client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2552 client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
2553
2554 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2556 assert!(acquired1.is_some());
2557
2558 let initial_olm_machine =
2561 client1.olm_machine().await.clone().expect("must have an olm machine");
2562
2563 let decryption_key = matrix_sdk_base::crypto::store::types::BackupDecryptionKey::new();
2566 let backup_key = decryption_key.megolm_v1_public_key();
2567 backup_key.set_version("1".to_owned());
2568 initial_olm_machine
2569 .backup_machine()
2570 .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
2571 .await
2572 .expect("Should save");
2573
2574 initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
2575
2576 assert!(client1.encryption().backups().are_enabled().await);
2577
2578 let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2580 assert!(acquired2.is_none());
2581
2582 drop(acquired1);
2584
2585 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2587 assert!(acquired1.is_some());
2588
2589 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2591 assert!(initial_olm_machine.same_as(&olm_machine));
2592
2593 drop(acquired1);
2595
2596 let acquired2 = client2.encryption().spin_lock_store(None).await.unwrap();
2598 assert!(acquired2.is_some());
2599
2600 drop(acquired2);
2602
2603 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2605 assert!(acquired1.is_some());
2606
2607 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2609
2610 assert!(!initial_olm_machine.same_as(&olm_machine));
2611
2612 let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
2613 assert!(backup_key_new.decryption_key.is_some());
2614 assert_eq!(
2615 backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
2616 backup_key.to_base64()
2617 );
2618 assert!(client1.encryption().backups().are_enabled().await);
2619 }
2620
2621 #[cfg(feature = "sqlite")]
2622 #[async_test]
2623 async fn test_generation_counter_no_spurious_invalidation() {
2624 use matrix_sdk_base::store::RoomLoadSettings;
2627 let tmp_dir = tempfile::tempdir().unwrap();
2628 let sqlite_path = tmp_dir.path().join("generation_counter_no_spurious_invalidations.db");
2629 let session = mock_matrix_session();
2630
2631 let client = Client::builder()
2632 .homeserver_url("http://localhost:1234")
2633 .request_config(RequestConfig::new().disable_retry())
2634 .sqlite_store(&sqlite_path, None)
2635 .build()
2636 .await
2637 .unwrap();
2638 client
2639 .matrix_auth()
2640 .restore_session(session.clone(), RoomLoadSettings::default())
2641 .await
2642 .unwrap();
2643
2644 let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2645
2646 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2647
2648 let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2650 assert!(initial_olm_machine.same_as(&after_enabling_lock));
2651
2652 {
2653 let client2 = Client::builder()
2655 .homeserver_url("http://localhost:1234")
2656 .request_config(RequestConfig::new().disable_retry())
2657 .sqlite_store(sqlite_path, None)
2658 .build()
2659 .await
2660 .unwrap();
2661 client2
2662 .matrix_auth()
2663 .restore_session(session, RoomLoadSettings::default())
2664 .await
2665 .unwrap();
2666
2667 client2
2668 .encryption()
2669 .enable_cross_process_store_lock("client2".to_owned())
2670 .await
2671 .unwrap();
2672
2673 let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2674 assert!(guard.is_some());
2675
2676 drop(guard);
2677 }
2678
2679 {
2680 let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2681 assert!(acquired.is_some());
2682 }
2683
2684 let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2686 assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2687
2688 {
2689 let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2690 assert!(acquired.is_some());
2691 }
2692
2693 let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2695 assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2696 }
2697
2698 #[async_test]
2699 async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2700 let client = no_retry_test_client(None).await;
2702 let server = MockServer::start().await;
2703
2704 let mut verification_state = client.encryption().verification_state();
2706
2707 assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2709
2710 let keys_requested = Arc::new(AtomicBool::new(false));
2713 let inner_bool = keys_requested.clone();
2714
2715 Mock::given(method("GET"))
2716 .and(path_regex(
2717 r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2718 ))
2719 .respond_with(move |_req: &Request| {
2720 inner_bool.fetch_or(true, Ordering::SeqCst);
2721 ResponseTemplate::new(200).set_body_json(json!({}))
2722 })
2723 .mount(&server)
2724 .await;
2725
2726 set_client_session(&client).await;
2728
2729 assert!(keys_requested.load(Ordering::SeqCst).not());
2732 assert_next_matches_with_timeout!(verification_state, VerificationState::Unverified);
2733 }
2734
2735 #[test]
2736 fn test_oauth_reset_info_from_uiaa_info() {
2737 let auth_info = json!({
2738 "session": "dummy",
2739 "flows": [
2740 {
2741 "stages": [
2742 "org.matrix.cross_signing_reset"
2743 ]
2744 }
2745 ],
2746 "params": {
2747 "org.matrix.cross_signing_reset": {
2748 "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2749 }
2750 },
2751 "msg": "To reset..."
2752 });
2753
2754 let auth_info = serde_json::from_value(auth_info)
2755 .expect("We should be able to deserialize the UiaaInfo");
2756 OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2757 .expect("We should be able to fetch the cross-signing reset info from the auth info");
2758 }
2759
2760 #[test]
2761 fn test_duplicate_one_time_key_error_parsing() {
2762 let message = concat!(
2763 r#"One time key signed_curve25519:AAAAAAAAAAA already exists. "#,
2764 r#"Old key: {"key":"dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs","#,
2765 r#""signatures":{"@example:matrix.org":{"ed25519:AAAAAAAAAA":""#,
2766 r#"Fk45zHAbrd+1j9wZXLjL2Y/+DU/Mnz9yuvlfYBOOT7qExN2Jdud+5BAuNs8nZ/caS4wTF39Kg3zQpzaGERoCBg"}}};"#,
2767 r#" new key: {'key': 'CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno', "#,
2768 r#"'signatures': {'@example:matrix.org': {'ed25519:AAAAAAAAAA': "#,
2769 r#"'BQ9Gp0p+6srF+c8OyruqKKd9R4yaub3THYAyyBB/7X/rG8BwcAqFynzl1aGyFYun4Q+087a5OSiglCXI+/kQAA'}}}"#
2770 );
2771 let message = DuplicateOneTimeKeyErrorMessage::from_str(message)
2772 .expect("We should be able to parse the error message");
2773
2774 assert_eq!(message.old_key.to_base64(), "dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs");
2775 assert_eq!(message.new_key.to_base64(), "CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno");
2776
2777 DuplicateOneTimeKeyErrorMessage::from_str("One time key already exists.")
2778 .expect_err("We shouldn't be able to parse an incomplete error message");
2779 }
2780
2781 fn devices_to_verify_against_keys_query_response(
2785 devices: Vec<serde_json::Value>,
2786 ) -> serde_json::Value {
2787 let device_keys: serde_json::Map<String, serde_json::Value> = devices
2788 .into_iter()
2789 .map(|device| (device.get("device_id").unwrap().as_str().unwrap().to_owned(), device))
2790 .collect();
2791 json!({
2792 "device_keys": {
2793 "@example:localhost": device_keys,
2794 },
2795 "master_keys": {
2796 "@example:localhost": {
2797 "keys": {
2798 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU",
2799 },
2800 "usage": ["master"],
2801 "user_id": "@example:localhost",
2802 },
2803 },
2804 "self_signing_keys": {
2805 "@example:localhost": {
2806 "keys": {
2807 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM",
2808 },
2809 "usage": ["self_signing"],
2810 "user_id": "@example:localhost",
2811 "signatures": {
2812 "@example:localhost": {
2813 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "etO1bB+rCk+TQ/FcjQ8eWu/RsRNQNNQ1Ek+PD6//j8yz6igRjfvuHZaMvr/quAFrirfgExph2TdOwlDgN5bFCQ",
2814 },
2815 },
2816 },
2817 },
2818 "user_signing_keys": {
2819 "@example:localhost": {
2820 "keys": {
2821 "ed25519:CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0": "CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0",
2822 },
2823 "usage": ["user_signing"],
2824 "user_id": "@example:localhost",
2825 "signatures": {
2826 "@example:localhost": {
2827 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "E/DFi/hQTIb/7eSB+HbCXeTLFaLjqWHzLO9GwjL1qdhfO7ew4p6YdtXSH3T2YYr1dKCPteH/4nMYVwOhww2CBg",
2828 },
2829 },
2830 },
2831 }
2832 })
2833 }
2834
2835 #[async_test]
2838 async fn test_devices_to_verify_against_no_devices() {
2841 let server = MockServer::start().await;
2842 let client = logged_in_client(Some(server.uri())).await;
2843
2844 Mock::given(method("POST"))
2845 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2846 .respond_with(
2847 ResponseTemplate::new(200)
2848 .set_body_json(devices_to_verify_against_keys_query_response(vec![])),
2849 )
2850 .mount(&server)
2851 .await;
2852
2853 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2854 }
2855
2856 #[async_test]
2857 async fn test_devices_to_verify_against_cross_signed() {
2860 let server = MockServer::start().await;
2861 let client = logged_in_client(Some(server.uri())).await;
2862
2863 Mock::given(method("POST"))
2864 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2865 .respond_with(ResponseTemplate::new(200).set_body_json(
2866 devices_to_verify_against_keys_query_response(vec![
2867 json!({
2868 "algorithms": [
2869 "m.olm.v1.curve25519-aes-sha2",
2870 "m.megolm.v1.aes-sha2",
2871 ],
2872 "user_id": "@example:localhost",
2873 "device_id": "SIGNEDDEVICE",
2874 "keys": {
2875 "curve25519:SIGNEDDEVICE": "o1LqUtH/sqd3WF+BB2Qr77uw3sDmZhMOz68/IV9aHxs",
2876 "ed25519:SIGNEDDEVICE": "iVoEfMOoUqxXVMLdpZCOgvQuCrT3/kQWkBmB3Phi/lo",
2877 },
2878 "signatures": {
2879 "@example:localhost": {
2880 "ed25519:SIGNEDDEVICE": "C7yRu1fNrdD2EobVdtANMqk3LBtWtTRWrIU22xVS8/Om1kmA/luzek64R3N6JsZhYczVmZYBKhUC9kRvHHwOBg",
2881 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "frfh2HP28GclmGvwTic00Fj4nZCvm4RlRA6U56mnD5920hOi04+L055ojzp6ybZXvC/GQYfyTHwQXlUN1nvxBA",
2882 },
2883 },
2884 })
2885 ])
2886 ))
2887 .mount(&server)
2888 .await;
2889
2890 assert!(client.encryption().has_devices_to_verify_against().await.unwrap());
2891 }
2892
2893 #[async_test]
2894 async fn test_devices_to_verify_against_dehydrated_and_unsigned() {
2897 let server = MockServer::start().await;
2898 let client = logged_in_client(Some(server.uri())).await;
2899 let user_id = client.user_id().unwrap();
2900 let olm_machine = client.olm_machine().await;
2901 let olm_machine = olm_machine.as_ref().unwrap();
2902
2903 Mock::given(method("POST"))
2904 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2905 .respond_with(ResponseTemplate::new(200).set_body_json(
2906 devices_to_verify_against_keys_query_response(vec![
2907 json!({
2908 "algorithms": [
2909 "m.olm.v1.curve25519-aes-sha2",
2910 "m.megolm.v1.aes-sha2",
2911 ],
2912 "user_id": "@example:localhost",
2913 "device_id": "DEHYDRATEDDEVICE",
2914 "keys": {
2915 "curve25519:DEHYDRATEDDEVICE": "XOn5VguAgokZ3p9mBz2yOB395fn6j75G8jIPcXEWQGY",
2916 "ed25519:DEHYDRATEDDEVICE": "4GG5xmBT7z4rgUgmWNlKZ+ABE3QlGgTorF+luCnKfYI",
2917 },
2918 "dehydrated": true,
2919 "signatures": {
2920 "@example:localhost": {
2921 "ed25519:DEHYDRATEDDEVICE": "+OMasB7nzVlMV+zRDxkh4h8h/Q0bY42P1SPv7X2IURIelT5G+d+AYSmg30N4maphxEDBqt/vI8/lIr71exc3Dg",
2922 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "8DzynAgbYgXX1Md5d4Vw91Zstpoi4dpG7levFeVhi4psCAWuBnV76Qu1s2TGjQQ0CLDXEqcxxuX9X4eUK5TGCg",
2923 },
2924 },
2925 }),
2926 json!({
2927 "algorithms": [
2928 "m.olm.v1.curve25519-aes-sha2",
2929 "m.megolm.v1.aes-sha2",
2930 ],
2931 "user_id": "@example:localhost",
2932 "device_id": "UNSIGNEDDEVICE",
2933 "keys": {
2934 "curve25519:UNSIGNEDDEVICE": "mMby6NpprkHxj+ONfO9Z5lBqVUHJBMkrPFSNJhogBkg",
2935 "ed25519:UNSIGNEDDEVICE": "Zifq39ZDrlIaSRf0Hh22owEqXCPE+1JSSgs6LDlubwQ",
2936 },
2937 "signatures": {
2938 "@example:localhost": {
2939 "ed25519:UNSIGNEDDEVICE": "+L29RoDKoTufPGm/Bae65KHno7Z1H7GYhxSKpB4RQZRS7NrR29AMW1PVhEsIozYuDVEFuMZ0L8H3dlcaHxagBA",
2940 },
2941 },
2942 }),
2943 ])
2944 ))
2945 .mount(&server)
2946 .await;
2947
2948 let (request_id, request) = olm_machine.query_keys_for_users([user_id]);
2949 client.keys_query(&request_id, request.device_keys).await.unwrap();
2950
2951 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2952 }
2953
2954 #[cfg(feature = "experimental-send-custom-to-device")]
2955 mod resolve_recipient_devices {
2956 use matrix_sdk_test::async_test;
2957 use ruma::{
2958 OwnedDeviceId, device_id, owned_device_id, to_device::DeviceIdOrAllDevices, user_id,
2959 };
2960
2961 use super::super::Device;
2962 use crate::{Client, test_utils::mocks::MatrixMockServer};
2963
2964 const BOB_FIRST_DEVICE: &str = "B0B0B0B0B";
2965 const BOB_SECOND_DEVICE: &str = "B0B2B0B2";
2966 const BOB_THIRD_DEVICE: &str = "B0B3B0B3";
2967
2968 async fn alice_and_bob_with_two_bob_devices() -> (MatrixMockServer, Client, Client) {
2974 let server = MatrixMockServer::new().await;
2975 server.mock_crypto_endpoints_preset().await;
2976
2977 let (alice, bob) = server.set_up_alice_and_bob_for_encryption().await;
2978 assert_eq!(bob.device_id().unwrap(), device_id!(BOB_FIRST_DEVICE));
2979
2980 server
2981 .set_up_new_device_for_encryption(&bob, device_id!(BOB_SECOND_DEVICE), vec![&alice])
2982 .await;
2983
2984 server
2986 .mock_sync()
2987 .ok_and_run(&alice, |builder| {
2988 builder.add_change_device(bob.user_id().unwrap());
2989 })
2990 .await;
2991
2992 (server, alice, bob)
2993 }
2994
2995 fn device_ids(devices: Vec<Device>) -> Vec<OwnedDeviceId> {
2998 let mut device_ids: Vec<_> =
2999 devices.into_iter().map(|d| d.device_id().to_owned()).collect();
3000 device_ids.sort();
3001 device_ids
3002 }
3003
3004 #[async_test]
3005 async fn test_all_devices_resolves_to_every_known_device() {
3006 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3007
3008 let (devices, unknown_devices) = alice
3009 .resolve_recipient_devices(
3010 bob.user_id().unwrap(),
3011 vec![DeviceIdOrAllDevices::AllDevices],
3012 )
3013 .await
3014 .unwrap();
3015
3016 assert_eq!(
3017 device_ids(devices),
3018 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
3019 );
3020 assert!(unknown_devices.is_empty(), "`AllDevices` can't reference an unknown device");
3021 }
3022
3023 #[async_test]
3024 async fn test_explicit_device_ids_are_filtered_to_the_requested_ones() {
3025 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3026
3027 let (devices, unknown_devices) = alice
3028 .resolve_recipient_devices(
3029 bob.user_id().unwrap(),
3030 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_SECOND_DEVICE))],
3031 )
3032 .await
3033 .unwrap();
3034
3035 assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_SECOND_DEVICE)]);
3036 assert!(unknown_devices.is_empty());
3037 }
3038
3039 #[async_test]
3041 async fn test_several_explicit_device_ids_are_all_resolved() {
3042 let (server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3043
3044 server
3045 .set_up_new_device_for_encryption(&bob, device_id!(BOB_THIRD_DEVICE), vec![&alice])
3046 .await;
3047
3048 server
3050 .mock_sync()
3051 .ok_and_run(&alice, |builder| {
3052 builder.add_change_device(bob.user_id().unwrap());
3053 })
3054 .await;
3055
3056 let (devices, unknown_devices) = alice
3057 .resolve_recipient_devices(
3058 bob.user_id().unwrap(),
3059 vec![
3060 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3061 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_THIRD_DEVICE)),
3062 ],
3063 )
3064 .await
3065 .unwrap();
3066
3067 assert_eq!(
3068 device_ids(devices),
3069 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_THIRD_DEVICE)],
3070 "the second device was not requested and must be left out"
3071 );
3072 assert!(unknown_devices.is_empty());
3073 }
3074
3075 #[async_test]
3076 async fn test_unknown_device_is_reported_back() {
3077 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3078
3079 let (devices, unknown_devices) = alice
3080 .resolve_recipient_devices(
3081 bob.user_id().unwrap(),
3082 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE"))],
3083 )
3084 .await
3085 .unwrap();
3086
3087 assert!(devices.is_empty());
3088 assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3089 }
3090
3091 #[async_test]
3094 async fn test_known_and_unknown_devices_are_split() {
3095 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3096
3097 let (devices, unknown_devices) = alice
3098 .resolve_recipient_devices(
3099 bob.user_id().unwrap(),
3100 vec![
3101 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3102 DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3103 ],
3104 )
3105 .await
3106 .unwrap();
3107
3108 assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3109 assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3110 }
3111
3112 #[async_test]
3116 async fn test_all_devices_takes_precedence_over_explicit_device_ids() {
3117 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3118
3119 let (devices, unknown_devices) = alice
3120 .resolve_recipient_devices(
3121 bob.user_id().unwrap(),
3122 vec![
3123 DeviceIdOrAllDevices::AllDevices,
3124 DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3125 ],
3126 )
3127 .await
3128 .unwrap();
3129
3130 assert_eq!(
3131 device_ids(devices),
3132 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
3133 );
3134 assert!(unknown_devices.is_empty());
3135 }
3136
3137 #[async_test]
3140 async fn test_empty_recipient_list_resolves_to_no_device() {
3141 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3142
3143 let (devices, unknown_devices) =
3144 alice.resolve_recipient_devices(bob.user_id().unwrap(), vec![]).await.unwrap();
3145
3146 assert!(devices.is_empty());
3147 assert!(unknown_devices.is_empty());
3148 }
3149
3150 #[async_test]
3153 async fn test_unknown_user_has_no_device() {
3154 let (_server, alice, _bob) = alice_and_bob_with_two_bob_devices().await;
3155 let unknown_user_id = user_id!("@carol:example.org");
3156
3157 let (devices, unknown_devices) = alice
3158 .resolve_recipient_devices(
3159 unknown_user_id,
3160 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE))],
3161 )
3162 .await
3163 .unwrap();
3164
3165 assert!(devices.is_empty());
3166 assert_eq!(unknown_devices, vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3167
3168 let (devices, unknown_devices) = alice
3171 .resolve_recipient_devices(unknown_user_id, vec![DeviceIdOrAllDevices::AllDevices])
3172 .await
3173 .unwrap();
3174
3175 assert!(devices.is_empty());
3176 assert!(unknown_devices.is_empty());
3177 }
3178 }
3179}