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::{
74 AuthData, AuthType, OAuth as OAuthData, OAuthParams, Password, UiaaInfo,
75 UserIdentifier,
76 },
77 },
78 error::{ErrorBody, StandardErrorBody},
79 },
80 assign,
81 events::room::{
82 MediaSource, ThumbnailInfo,
83 member::{MembershipChange, OriginalSyncRoomMemberEvent},
84 },
85};
86#[cfg(feature = "experimental-send-custom-to-device")]
87use ruma::{
88 events::{AnyToDeviceEventContent, ToDeviceEventType},
89 serde::Raw,
90 to_device::DeviceIdOrAllDevices,
91};
92use serde::{Deserialize, de::Error as _};
93use tasks::BundleReceiverTask;
94use tokio::sync::{Mutex, RwLockReadGuard};
95use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
96use tracing::{Instrument, Span, debug, error, instrument, warn};
97use url::Url;
98use vodozemac::Curve25519PublicKey;
99
100use self::{
101 backups::{Backups, types::BackupClientState},
102 futures::UploadEncryptedFile,
103 identities::{Device, DeviceUpdates, IdentityUpdates, UserDevices, UserIdentity},
104 recovery::{Recovery, RecoveryState},
105 secret_storage::SecretStorage,
106 tasks::{BackupDownloadTask, BackupUploadingTask, ClientTasks},
107 verification::{SasVerification, Verification, VerificationRequest},
108};
109use crate::{
110 Client, Error, HttpError, Result, Room, TransmissionProgress,
111 attachment::Thumbnail,
112 client::{ClientInner, WeakClient},
113 cross_process_lock::CrossProcessLockGuard,
114 error::HttpResult,
115};
116
117pub mod backups;
118pub mod dehydrated_devices;
119pub mod futures;
120pub mod identities;
121pub mod recovery;
122pub mod secret_storage;
123pub(crate) mod tasks;
124pub mod verification;
125
126use matrix_sdk_base::crypto::OlmMachineBuilder;
127pub use matrix_sdk_base::crypto::{
128 CrossSigningStatus, CryptoStoreError, DecryptorError, EventError, KeyExportError, LocalTrust,
129 MediaEncryptionInfo, MegolmError, OlmError, RoomKeyImportResult, SessionCreationError,
130 SignatureError, VERSION,
131 olm::{
132 SessionCreationError as MegolmSessionCreationError,
133 SessionExportError as OlmSessionExportError,
134 },
135 vodozemac,
136};
137use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
138
139#[cfg(feature = "experimental-send-custom-to-device")]
140use crate::config::RequestConfig;
141pub use crate::error::RoomKeyImportError;
142
143#[cfg(feature = "sqlite")]
146#[derive(Debug, thiserror::Error)]
147pub enum BundleExportError {
148 #[error(transparent)]
150 OpenStoreError(#[from] matrix_sdk_sqlite::OpenStoreError),
151 #[error(transparent)]
153 StoreError(#[from] CryptoStoreError),
154 #[error(transparent)]
157 SecretExport(#[from] matrix_sdk_base::crypto::store::SecretsBundleExportError),
158}
159
160#[derive(Debug, thiserror::Error)]
163pub enum BundleImportError {
164 #[error(transparent)]
166 SecretImport(#[from] SecretImportError),
167 #[error(transparent)]
169 DeviceKeys(#[from] Error),
170}
171
172#[cfg(feature = "sqlite")]
181pub async fn export_secrets_bundle_from_store(
182 database_path: impl AsRef<Path>,
183 passphrase: Option<&str>,
184) -> std::result::Result<Option<(OwnedUserId, SecretsBundle)>, BundleExportError> {
185 use matrix_sdk_base::crypto::store::CryptoStore;
186
187 let store = matrix_sdk_sqlite::SqliteCryptoStore::open(database_path, passphrase).await?;
188 let account =
189 store.load_account().await.map_err(|e| BundleExportError::StoreError(e.into()))?;
190
191 if let Some(account) = account {
192 let machine = OlmMachineBuilder::new(&account.user_id, &account.device_id)
193 .with_crypto_store(store)
194 .build()
195 .await
196 .map_err(BundleExportError::StoreError)?;
197
198 let bundle = machine.store().export_secrets_bundle().await?;
199
200 Ok(Some((account.user_id.to_owned(), bundle)))
201 } else {
202 Ok(None)
203 }
204}
205
206pub(crate) struct EncryptionData {
208 pub tasks: StdMutex<ClientTasks>,
211
212 pub encryption_settings: EncryptionSettings,
214
215 pub backup_state: BackupClientState,
217
218 pub recovery_state: SharedObservable<RecoveryState>,
220
221 pub dehydrated_devices_state: dehydrated_devices::DehydratedDevicesState,
224}
225
226impl EncryptionData {
227 pub fn new(encryption_settings: EncryptionSettings) -> Self {
228 Self {
229 encryption_settings,
230
231 tasks: StdMutex::new(Default::default()),
232 backup_state: Default::default(),
233 recovery_state: Default::default(),
234 dehydrated_devices_state: Default::default(),
235 }
236 }
237
238 pub fn initialize_tasks(&self, client: &Arc<ClientInner>) {
239 let weak_client = WeakClient::from_inner(client);
240
241 let mut tasks = self.tasks.lock();
242 tasks.upload_room_keys = Some(BackupUploadingTask::new(weak_client.clone()));
243
244 if self.encryption_settings.backup_download_strategy
245 == BackupDownloadStrategy::AfterDecryptionFailure
246 {
247 tasks.download_room_keys = Some(BackupDownloadTask::new(weak_client));
248 }
249 }
250
251 pub fn initialize_recovery_state_update_task(&self, client: &Client) {
257 let mut guard = self.tasks.lock();
258
259 let future = Recovery::update_state_after_backup_state_change(client);
260 let join_handle = spawn(future);
261
262 guard.update_recovery_state_after_backup = Some(join_handle);
263 }
264}
265
266#[derive(Clone, Copy, Debug, Default)]
268pub struct EncryptionSettings {
269 pub auto_enable_cross_signing: bool,
275
276 pub backup_download_strategy: BackupDownloadStrategy,
281
282 pub auto_enable_backups: bool,
284}
285
286#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
288#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
289pub enum BackupDownloadStrategy {
290 OneShot,
301
302 AfterDecryptionFailure,
304
305 #[default]
310 Manual,
311}
312
313#[derive(Clone, Copy, Debug, Eq, PartialEq)]
318pub enum VerificationState {
319 Unknown,
321 Verified,
324 Unverified,
326}
327
328#[derive(Debug)]
339pub struct CrossSigningResetHandle {
340 client: Client,
341 upload_request: UploadSigningKeysRequest,
342 signatures_request: UploadSignaturesRequest,
343 auth_type: CrossSigningResetAuthType,
344 is_cancelled: Mutex<bool>,
345}
346
347impl CrossSigningResetHandle {
348 pub fn new(
350 client: Client,
351 upload_request: UploadSigningKeysRequest,
352 signatures_request: UploadSignaturesRequest,
353 auth_type: CrossSigningResetAuthType,
354 ) -> Self {
355 Self {
356 client,
357 upload_request,
358 signatures_request,
359 auth_type,
360 is_cancelled: Mutex::new(false),
361 }
362 }
363
364 pub fn auth_type(&self) -> &CrossSigningResetAuthType {
367 &self.auth_type
368 }
369
370 pub async fn auth(&self, auth: AuthData) -> Result<()> {
374 const RETRY_EVERY: Duration = Duration::from_millis(500);
376
377 const TIMEOUT: Duration = Duration::from_mins(2);
379
380 timeout(
381 async {
382 let mut upload_request = self.upload_request.clone();
383 upload_request.auth = Some(auth);
384
385 debug!(
386 "Repeatedly PUTting to keys/device_signing/upload until it works \
387 or we hit a permanent failure."
388 );
389 while let Err(e) = self.client.send(upload_request.clone()).await {
390 if *self.is_cancelled.lock().await {
391 return Ok(());
392 }
393
394 match e.as_uiaa_response() {
395 Some(uiaa_info) => {
396 if !matches!(self.auth_type, CrossSigningResetAuthType::OAuth(_))
399 && uiaa_info.auth_error.is_some()
400 {
401 return Err(e.into());
402 }
403 }
404 None => return Err(e.into()),
405 }
406
407 debug!(
408 "PUT to keys/device_signing/upload failed with 401. Retrying after \
409 a short delay."
410 );
411 sleep(RETRY_EVERY).await;
412 }
413
414 self.client.send(self.signatures_request.clone()).await?;
415
416 Ok(())
417 },
418 TIMEOUT,
419 )
420 .await
421 .unwrap_or_else(|_| {
422 warn!("Timed out waiting for keys/device_signing/upload to succeed.");
423 Err(Error::Timeout)
424 })?;
425
426 if let Some(olm) = self.client.olm_machine().await.as_ref() {
427 self.client.encryption().query_own_keys(olm).await;
428 }
429
430 Ok(())
431 }
432
433 pub async fn cancel(&self) {
435 *self.is_cancelled.lock().await = true;
436 }
437}
438
439#[derive(Debug, Clone)]
442pub enum CrossSigningResetAuthType {
443 Uiaa(UiaaCrossSigningResetInfo),
445 OAuth(OAuthCrossSigningResetInfo),
448}
449
450impl CrossSigningResetAuthType {
451 fn new(error: &HttpError) -> Result<Option<Self>> {
452 if let Some(auth_info) = error.as_uiaa_response() {
453 if let Ok(Some(auth_info)) = OAuthCrossSigningResetInfo::from_auth_info(auth_info) {
454 Ok(Some(CrossSigningResetAuthType::OAuth(auth_info)))
455 } else {
456 Ok(Some(CrossSigningResetAuthType::Uiaa(
457 UiaaCrossSigningResetInfo::from_auth_info(auth_info)?,
458 )))
459 }
460 } else {
461 Ok(None)
462 }
463 }
464}
465
466#[derive(Debug, Clone)]
469pub struct UiaaCrossSigningResetInfo {
470 auth_info: UiaaInfo,
471}
472
473impl UiaaCrossSigningResetInfo {
474 fn from_auth_info(auth_info: &UiaaInfo) -> Result<Self> {
475 if auth_info.session.is_none() {
476 return Err(serde_json::Error::custom("UIAA info is missing a session token").into());
477 }
478
479 Ok(UiaaCrossSigningResetInfo { auth_info: auth_info.clone() })
480 }
481
482 pub fn session(&self) -> &str {
484 self.auth_info.session.as_deref().expect("The session is checked in from_auth_info")
485 }
486
487 pub fn auth_info(&self) -> &UiaaInfo {
490 &self.auth_info
491 }
492
493 pub fn as_password_auth_data(&self, identifier: UserIdentifier, password: String) -> AuthData {
499 AuthData::Password(assign!(Password::new(identifier, password), {
500 session: Some(self.session().to_owned()),
501 }))
502 }
503}
504
505#[derive(Debug, Clone, Deserialize)]
508pub struct OAuthCrossSigningResetInfo {
509 pub approval_url: Url,
511
512 pub session: String,
514}
515
516impl OAuthCrossSigningResetInfo {
517 fn from_auth_info(auth_info: &UiaaInfo) -> Result<Option<Self>> {
518 let Some(parameters) = auth_info.params::<OAuthParams>(&AuthType::OAuth)? else {
519 return Ok(None);
520 };
521
522 Ok(Some(OAuthCrossSigningResetInfo {
523 approval_url: parameters.url.as_str().try_into()?,
524 session: auth_info
525 .session
526 .as_ref()
527 .ok_or_else(|| serde_json::Error::custom("UIAA info is missing a session token"))?
528 .to_owned(),
529 }))
530 }
531
532 pub fn as_auth_data(&self) -> AuthData {
534 AuthData::OAuth(assign!(OAuthData::new(), { session: Some(self.session.clone()) }))
535 }
536}
537
538#[derive(Clone, Debug)]
541pub struct DuplicateOneTimeKeyErrorMessage {
542 pub old_key: Curve25519PublicKey,
544 pub new_key: Curve25519PublicKey,
546}
547
548impl FromStr for DuplicateOneTimeKeyErrorMessage {
549 type Err = serde_json::Error;
550
551 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
552 let mut split = s.split_terminator(';');
556
557 let old_key = split
558 .next()
559 .ok_or(serde_json::Error::custom("Old key is missing in the error message"))?;
560 let new_key = split
561 .next()
562 .ok_or(serde_json::Error::custom("New key is missing in the error message"))?;
563
564 let old_key_index = old_key
567 .find("Old key:")
568 .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
569
570 let old_key = old_key[old_key_index..]
571 .trim()
572 .strip_prefix("Old key:")
573 .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
574
575 let new_key = new_key
578 .trim()
579 .strip_prefix("new key:")
580 .ok_or(serde_json::Error::custom("New key is missing the prefix"))?;
581
582 let new_key = new_key.replace("'", "\"");
585
586 let old_key: SignedKey = serde_json::from_str(old_key)?;
588 let new_key: SignedKey = serde_json::from_str(&new_key)?;
589
590 let old_key = old_key.key();
592 let new_key = new_key.key();
593
594 Ok(Self { old_key, new_key })
595 }
596}
597
598impl Client {
599 pub(crate) async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
600 self.base_client().olm_machine().await
601 }
602
603 pub(crate) async fn mark_request_as_sent(
604 &self,
605 request_id: &TransactionId,
606 response: impl Into<matrix_sdk_base::crypto::types::requests::AnyIncomingResponse<'_>>,
607 ) -> Result<(), matrix_sdk_base::Error> {
608 Ok(self
609 .olm_machine()
610 .await
611 .as_ref()
612 .expect(
613 "We should have an olm machine once we try to mark E2EE related requests as sent",
614 )
615 .mark_request_as_sent(request_id, response)
616 .await?)
617 }
618
619 #[instrument(skip(self, device_keys))]
625 pub(crate) async fn keys_query(
626 &self,
627 request_id: &TransactionId,
628 device_keys: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
629 ) -> Result<get_keys::v3::Response> {
630 let request = assign!(get_keys::v3::Request::new(), { device_keys });
631
632 let response = self.send(request).await?;
633 self.mark_request_as_sent(request_id, &response).await?;
634 self.encryption().update_state_after_keys_query(&response).await;
635
636 Ok(response)
637 }
638
639 pub fn upload_encrypted_file<'a, R: Read + ?Sized + 'a>(
673 &'a self,
674 reader: &'a mut R,
675 ) -> UploadEncryptedFile<'a, R> {
676 UploadEncryptedFile::new(self, reader)
677 }
678
679 pub(crate) async fn upload_encrypted_media_and_thumbnail(
682 &self,
683 data: &[u8],
684 thumbnail: Option<Thumbnail>,
685 send_progress: SharedObservable<TransmissionProgress>,
686 ) -> Result<(MediaSource, Option<(MediaSource, Box<ThumbnailInfo>)>)> {
687 let upload_thumbnail = self.upload_encrypted_thumbnail(thumbnail, send_progress.clone());
688
689 let upload_attachment = async {
690 let mut cursor = Cursor::new(data);
691 self.upload_encrypted_file(&mut cursor)
692 .with_send_progress_observable(send_progress)
693 .await
694 };
695
696 let (thumbnail, file) = try_join(upload_thumbnail, upload_attachment).await?;
697
698 Ok((MediaSource::Encrypted(Box::new(file)), thumbnail))
699 }
700
701 async fn upload_encrypted_thumbnail(
704 &self,
705 thumbnail: Option<Thumbnail>,
706 send_progress: SharedObservable<TransmissionProgress>,
707 ) -> Result<Option<(MediaSource, Box<ThumbnailInfo>)>> {
708 let Some(thumbnail) = thumbnail else {
709 return Ok(None);
710 };
711
712 let (data, _, thumbnail_info) = thumbnail.into_parts();
713 let mut cursor = Cursor::new(data);
714
715 let file = self
716 .upload_encrypted_file(&mut cursor)
717 .with_send_progress_observable(send_progress)
718 .await?;
719
720 Ok(Some((MediaSource::Encrypted(Box::new(file)), thumbnail_info)))
721 }
722
723 pub(crate) async fn claim_one_time_keys(
729 &self,
730 users: impl Iterator<Item = &UserId>,
731 ) -> Result<()> {
732 let _lock = self.locks().key_claim_lock.lock().await;
733
734 if let Some((request_id, request)) = self
735 .olm_machine()
736 .await
737 .as_ref()
738 .ok_or(Error::NoOlmMachine)?
739 .get_missing_sessions(users)
740 .await?
741 {
742 let response = self.send(request).await?;
743 self.mark_request_as_sent(&request_id, &response).await?;
744 }
745
746 Ok(())
747 }
748
749 #[instrument(skip(self, request))]
759 pub(crate) async fn keys_upload(
760 &self,
761 request_id: &TransactionId,
762 request: &upload_keys::v3::Request,
763 ) -> Result<upload_keys::v3::Response> {
764 debug!(
765 device_keys = request.device_keys.is_some(),
766 one_time_key_count = request.one_time_keys.len(),
767 "Uploading public encryption keys",
768 );
769
770 let response = self.send(request.clone()).await?;
771 self.mark_request_as_sent(request_id, &response).await?;
772
773 Ok(response)
774 }
775
776 pub(crate) async fn room_send_helper(
777 &self,
778 request: &RoomMessageRequest,
779 ) -> Result<send_message_event::v3::Response> {
780 let content = request.content.clone();
781 let txn_id = request.txn_id.clone();
782 let room_id = &request.room_id;
783
784 self.get_room(room_id)
785 .expect("Can't send a message to a room that isn't known to the store")
786 .send(*content)
787 .with_transaction_id(txn_id)
788 .await
789 .map(|result| result.response)
790 }
791
792 pub(crate) async fn send_to_device(
793 &self,
794 request: &ToDeviceRequest,
795 ) -> HttpResult<ToDeviceResponse> {
796 let request = RumaToDeviceRequest::new_raw(
797 request.event_type.clone(),
798 request.txn_id.clone(),
799 request.messages.clone(),
800 );
801
802 self.send(request).await
803 }
804
805 pub(crate) async fn send_verification_request(
806 &self,
807 request: OutgoingVerificationRequest,
808 ) -> Result<()> {
809 use matrix_sdk_base::crypto::types::requests::OutgoingVerificationRequest::*;
810
811 match request {
812 ToDevice(t) => {
813 self.send_to_device(&t).await?;
814 }
815 InRoom(r) => {
816 self.room_send_helper(&r).await?;
817 }
818 }
819
820 Ok(())
821 }
822
823 async fn send_outgoing_request(&self, r: OutgoingRequest) -> Result<()> {
824 use matrix_sdk_base::crypto::types::requests::AnyOutgoingRequest;
825
826 match r.request() {
827 AnyOutgoingRequest::KeysQuery(request) => {
828 self.keys_query(r.request_id(), request.device_keys.clone()).await?;
829 }
830 AnyOutgoingRequest::KeysUpload(request) => {
831 let response = self.keys_upload(r.request_id(), request).await;
832
833 if let Err(e) = &response {
834 match e.as_client_api_error() {
835 Some(e) if e.status_code == 400 => {
836 if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &e.body
837 {
838 {
844 let already_reported = self
845 .state_store()
846 .get_kv_data(StateStoreDataKey::OneTimeKeyAlreadyUploaded)
847 .await?
848 .is_some();
849
850 if message.starts_with("One time key") && !already_reported {
851 let error_message =
852 DuplicateOneTimeKeyErrorMessage::from_str(message);
853
854 if let Ok(message) = &error_message {
855 error!(
856 sentry = true,
857 old_key = %message.old_key,
858 new_key = %message.new_key,
859 "Duplicate one-time keys have been uploaded"
860 );
861 } else {
862 error!(
863 sentry = true,
864 "Duplicate one-time keys have been uploaded"
865 );
866 }
867
868 self.state_store()
869 .set_kv_data(
870 StateStoreDataKey::OneTimeKeyAlreadyUploaded,
871 StateStoreDataValue::OneTimeKeyAlreadyUploaded,
872 )
873 .await?;
874
875 if let Err(e) = self
876 .inner
877 .duplicate_key_upload_error_sender
878 .send(error_message.ok())
879 {
880 error!(
881 "Failed to dispatch duplicate key upload error notification: {}",
882 e
883 );
884 }
885 }
886 }
887 }
888 }
889 _ => {}
890 }
891
892 response?;
893 }
894 }
895 AnyOutgoingRequest::ToDeviceRequest(request) => {
896 let response = self.send_to_device(request).await?;
897 self.mark_request_as_sent(r.request_id(), &response).await?;
898 }
899 AnyOutgoingRequest::SignatureUpload(request) => {
900 let response = self.send(request.clone()).await?;
901 self.mark_request_as_sent(r.request_id(), &response).await?;
902 }
903 AnyOutgoingRequest::RoomMessage(request) => {
904 let response = self.room_send_helper(request).await?;
905 self.mark_request_as_sent(r.request_id(), &response).await?;
906 }
907 AnyOutgoingRequest::KeysClaim(request) => {
908 let response = self.send(request.clone()).await?;
909 self.mark_request_as_sent(r.request_id(), &response).await?;
910 }
911 }
912
913 Ok(())
914 }
915
916 #[instrument(skip_all)]
917 pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
918 const MAX_CONCURRENT_REQUESTS: usize = 20;
919
920 if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
923 warn!("Error while claiming one-time keys {:?}", e);
924 }
925
926 let outgoing_requests = stream::iter(
927 self.olm_machine()
928 .await
929 .as_ref()
930 .ok_or(Error::NoOlmMachine)?
931 .outgoing_requests()
932 .await?,
933 )
934 .map(|r| self.send_outgoing_request(r));
935
936 let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
937
938 requests
939 .for_each(|r| async move {
940 match r {
941 Ok(_) => (),
942 Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
943 }
944 })
945 .await;
946
947 Ok(())
948 }
949}
950
951#[cfg(feature = "experimental-send-custom-to-device")]
952impl Client {
953 pub async fn send_encrypted_to_device(
981 &self,
982 event_type: &ToDeviceEventType,
983 recipients: BTreeMap<OwnedUserId, Vec<DeviceIdOrAllDevices>>,
984 content: Raw<AnyToDeviceEventContent>,
985 ) -> Result<BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>> {
986 let mut failures: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>> = BTreeMap::new();
987 let mut recipient_devices = Vec::<_>::new();
988
989 for (user_id, recipient_device_ids) in recipients {
991 let (devices, unknown_devices) =
992 self.resolve_recipient_devices(&user_id, recipient_device_ids).await?;
993 recipient_devices.extend(devices);
994 if !unknown_devices.is_empty() {
995 failures.insert(user_id, unknown_devices);
996 }
997 }
998
999 if !recipient_devices.is_empty() {
1000 let encrypt_and_send_failures = self
1001 .encryption()
1002 .encrypt_and_send_raw_to_device(
1003 recipient_devices.iter().collect(),
1004 &event_type.to_string(),
1005 content,
1006 CollectStrategy::AllDevices,
1007 )
1008 .await?;
1009
1010 for (user_id, device_id) in encrypt_and_send_failures {
1011 failures.entry(user_id).or_default().push(device_id)
1012 }
1013 }
1014
1015 Ok(failures)
1016 }
1017
1018 async fn resolve_recipient_devices(
1027 &self,
1028 user_id: &UserId,
1029 recipient_device_ids: Vec<DeviceIdOrAllDevices>,
1030 ) -> Result<(Vec<Device>, Vec<OwnedDeviceId>)> {
1031 let user_devices = self.encryption().get_user_devices(user_id).await?;
1032
1033 if recipient_device_ids.contains(&DeviceIdOrAllDevices::AllDevices) {
1034 let devices: Vec<_> = user_devices.devices().collect();
1038
1039 if devices.is_empty() {
1040 warn!(
1041 "Recipient list contains `AllDevices` but no devices found for user {user_id}."
1042 );
1043 }
1044 if recipient_device_ids.len() > 1 {
1045 warn!(
1046 "The recipient_device_ids list for {user_id} contains both `AllDevices` and explicit `DeviceId` entries. Only consider `AllDevices`",
1047 );
1048 }
1049
1050 Ok((devices, Vec::new()))
1051 } else {
1052 let (found_device_ids, devices): (BTreeSet<_>, Vec<_>) = user_devices
1055 .devices()
1056 .map(|device| (device.device_id().to_owned(), device))
1057 .filter(|(device_id, _)| {
1058 recipient_device_ids
1059 .contains(&DeviceIdOrAllDevices::DeviceId(device_id.clone()))
1060 })
1061 .unzip();
1062
1063 let requested_device_ids: BTreeSet<_> = recipient_device_ids
1064 .into_iter()
1065 .filter_map(|d| as_variant!(d, DeviceIdOrAllDevices::DeviceId))
1066 .collect();
1067
1068 let missing_devices =
1071 requested_device_ids.difference(&found_device_ids).map(ToOwned::to_owned).collect();
1072
1073 Ok((devices, missing_devices))
1074 }
1075 }
1076}
1077
1078#[cfg(any(feature = "testing", test))]
1079impl Client {
1080 pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
1082 self.olm_machine().await
1083 }
1084
1085 pub fn abort_bundle_receiver_task(&self) {
1087 let tasks = self.inner.e2ee.tasks.lock();
1088 if let Some(task) = tasks.receive_historic_room_key_bundles.as_ref() {
1089 task.abort()
1090 }
1091 }
1092}
1093
1094#[derive(Debug, Clone)]
1098pub struct Encryption {
1099 client: Client,
1101}
1102
1103impl Encryption {
1104 pub(crate) fn new(client: Client) -> Self {
1105 Self { client }
1106 }
1107
1108 pub(crate) fn settings(&self) -> EncryptionSettings {
1110 self.client.inner.e2ee.encryption_settings
1111 }
1112
1113 pub async fn ed25519_key(&self) -> Option<String> {
1116 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
1117 }
1118
1119 pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
1121 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
1122 }
1123
1124 pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
1126 match self.get_own_device().await {
1127 Ok(Some(device)) => device.first_time_seen_ts(),
1128 _ => MilliSecondsSinceUnixEpoch::now(),
1130 }
1131 }
1132
1133 pub async fn import_secrets_bundle(
1146 &self,
1147 bundle: &SecretsBundle,
1148 ) -> Result<(), BundleImportError> {
1149 self.import_secrets_bundle_impl(bundle).await?;
1150
1151 self.ensure_device_keys_upload().await?;
1154 self.wait_for_e2ee_initialization_tasks().await;
1155
1156 if !self.backups().are_enabled().await {
1161 self.backups().maybe_resume_backups().await?;
1162 }
1163
1164 Ok(())
1165 }
1166
1167 pub(crate) async fn import_secrets_bundle_impl(
1168 &self,
1169 bundle: &SecretsBundle,
1170 ) -> Result<(), SecretImportError> {
1171 let olm_machine = self.client.olm_machine().await;
1172 let olm_machine =
1173 olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
1174
1175 olm_machine.store().import_secrets_bundle(bundle).await
1176 }
1177
1178 pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
1183 let olm = self.client.olm_machine().await;
1184 let machine = olm.as_ref()?;
1185 Some(machine.cross_signing_status().await)
1186 }
1187
1188 pub async fn has_devices_to_verify_against(&self) -> Result<bool> {
1194 let olm_machine = self.client.olm_machine().await;
1195 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1196
1197 self.ensure_initial_key_query().await?;
1198
1199 let devices = olm_machine.get_user_devices(olm_machine.user_id(), None).await?;
1200
1201 Ok(devices.has_other_devices_to_verify_against())
1202 }
1203
1204 pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
1209 if let Some(machine) = self.client.olm_machine().await.as_ref() {
1210 machine.tracked_users().await
1211 } else {
1212 Ok(HashSet::new())
1213 }
1214 }
1215
1216 pub fn verification_state(&self) -> Subscriber<VerificationState> {
1239 self.client.inner.verification_state.subscribe_reset()
1240 }
1241
1242 pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
1244 let olm = self.client.olm_machine().await;
1245 let olm = olm.as_ref()?;
1246 #[allow(clippy::bind_instead_of_map)]
1247 olm.get_verification(user_id, flow_id).and_then(|v| match v {
1248 matrix_sdk_base::crypto::Verification::SasV1(sas) => {
1249 Some(SasVerification { inner: sas, client: self.client.clone() }.into())
1250 }
1251 #[cfg(feature = "qrcode")]
1252 matrix_sdk_base::crypto::Verification::QrV1(qr) => {
1253 Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
1254 }
1255 _ => None,
1256 })
1257 }
1258
1259 pub async fn get_verification_request(
1262 &self,
1263 user_id: &UserId,
1264 flow_id: impl AsRef<str>,
1265 ) -> Option<VerificationRequest> {
1266 let olm = self.client.olm_machine().await;
1267 let olm = olm.as_ref()?;
1268
1269 olm.get_verification_request(user_id, flow_id)
1270 .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
1271 }
1272
1273 pub async fn get_device(
1306 &self,
1307 user_id: &UserId,
1308 device_id: &DeviceId,
1309 ) -> Result<Option<Device>, CryptoStoreError> {
1310 let olm = self.client.olm_machine().await;
1311 let Some(machine) = olm.as_ref() else { return Ok(None) };
1312 let device = machine.get_device(user_id, device_id, None).await?;
1313 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1314 }
1315
1316 pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
1323 let olm = self.client.olm_machine().await;
1324 let Some(machine) = olm.as_ref() else { return Ok(None) };
1325 let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
1326 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1327 }
1328
1329 pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
1355 let devices = self
1356 .client
1357 .olm_machine()
1358 .await
1359 .as_ref()
1360 .ok_or(Error::NoOlmMachine)?
1361 .get_user_devices(user_id, None)
1362 .await?;
1363
1364 Ok(UserDevices { inner: devices, client: self.client.clone() })
1365 }
1366
1367 pub async fn get_user_identity(
1403 &self,
1404 user_id: &UserId,
1405 ) -> Result<Option<UserIdentity>, CryptoStoreError> {
1406 let olm = self.client.olm_machine().await;
1407 let Some(olm) = olm.as_ref() else { return Ok(None) };
1408 let identity = olm.get_identity(user_id, None).await?;
1409
1410 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1411 }
1412
1413 pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
1451 let olm = self.client.olm_machine().await;
1452 let Some(olm) = olm.as_ref() else { return Ok(None) };
1453
1454 let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
1455 self.client.keys_query(&request_id, request.device_keys).await?;
1456
1457 let identity = olm.get_identity(user_id, None).await?;
1458 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1459 }
1460
1461 pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates> + use<>> {
1493 let olm = self.client.olm_machine().await;
1494 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1495 let client = self.client.to_owned();
1496
1497 Ok(olm
1498 .store()
1499 .devices_stream()
1500 .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1501 }
1502
1503 pub async fn user_identities_stream(
1532 &self,
1533 ) -> Result<impl Stream<Item = IdentityUpdates> + use<>> {
1534 let olm = self.client.olm_machine().await;
1535 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1536 let client = self.client.to_owned();
1537
1538 Ok(olm
1539 .store()
1540 .user_identities_stream()
1541 .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1542 }
1543
1544 pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1588 let olm = self.client.olm_machine().await;
1589 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1590
1591 let CrossSigningBootstrapRequests {
1592 upload_signing_keys_req,
1593 upload_keys_req,
1594 upload_signatures_req,
1595 } = olm.bootstrap_cross_signing(false).await?;
1596
1597 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1598 auth: auth_data,
1599 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1600 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1601 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1602 });
1603
1604 if let Some(req) = upload_keys_req {
1605 self.client.send_outgoing_request(req).await?;
1606 }
1607 self.client.send(upload_signing_keys_req).await?;
1608 self.client.send(upload_signatures_req).await?;
1609 self.query_own_keys(olm).await;
1610
1611 Ok(())
1612 }
1613
1614 pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1648 let olm = self.client.olm_machine().await;
1649 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1650
1651 let CrossSigningBootstrapRequests {
1652 upload_keys_req,
1653 upload_signing_keys_req,
1654 upload_signatures_req,
1655 } = olm.bootstrap_cross_signing(true).await?;
1656
1657 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1658 auth: None,
1659 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1660 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1661 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1662 });
1663
1664 if let Some(req) = upload_keys_req {
1665 self.client.send_outgoing_request(req).await?;
1666 }
1667
1668 if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1669 if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1670 let client = self.client.clone();
1671
1672 Ok(Some(CrossSigningResetHandle::new(
1673 client,
1674 upload_signing_keys_req,
1675 upload_signatures_req,
1676 auth_type,
1677 )))
1678 } else {
1679 Err(error.into())
1680 }
1681 } else {
1682 self.client.send(upload_signatures_req).await?;
1683 self.query_own_keys(olm).await;
1684
1685 Ok(None)
1686 }
1687 }
1688
1689 async fn query_own_keys(&self, olm: &OlmMachine) {
1695 let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
1696 if let Err(error) = self.client.keys_query(&request_id, request.device_keys).await {
1697 warn!(
1698 "Couldn't query our own keys after uploading cross-signing signatures: {error:?}"
1699 );
1700 }
1701 }
1702
1703 async fn ensure_initial_key_query(&self) -> Result<()> {
1706 let olm_machine = self.client.olm_machine().await;
1707 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1708
1709 let user_id = olm_machine.user_id();
1710
1711 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1712 let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1713 self.client.keys_query(&request_id, request.device_keys).await?;
1714 }
1715
1716 Ok(())
1717 }
1718
1719 pub async fn bootstrap_cross_signing_if_needed(
1768 &self,
1769 auth_data: Option<AuthData>,
1770 ) -> Result<()> {
1771 let olm_machine = self.client.olm_machine().await;
1772 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1773 let user_id = olm_machine.user_id();
1774
1775 self.ensure_initial_key_query().await?;
1776
1777 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1778 self.bootstrap_cross_signing(auth_data).await?;
1779 }
1780
1781 Ok(())
1782 }
1783
1784 #[cfg(not(target_family = "wasm"))]
1835 pub async fn export_room_keys(
1836 &self,
1837 path: PathBuf,
1838 passphrase: &str,
1839 predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1840 ) -> Result<()> {
1841 let olm = self.client.olm_machine().await;
1842 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1843
1844 let keys = olm.store().export_room_keys(predicate).await?;
1845 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1846
1847 let encrypt = move || -> Result<()> {
1848 let export: String =
1849 matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1850 let mut file = std::fs::File::create(path)?;
1851 file.write_all(&export.into_bytes())?;
1852 Ok(())
1853 };
1854
1855 let task = tokio::task::spawn_blocking(encrypt);
1856 task.await.expect("Task join error")
1857 }
1858
1859 #[cfg(not(target_family = "wasm"))]
1896 pub async fn import_room_keys(
1897 &self,
1898 path: PathBuf,
1899 passphrase: &str,
1900 ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1901 let olm = self.client.olm_machine().await;
1902 let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1903 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1904
1905 let decrypt = move || {
1906 let file = std::fs::File::open(path)?;
1907 matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1908 };
1909
1910 let task = tokio::task::spawn_blocking(decrypt);
1911 let import = task.await.expect("Task join error")?;
1912
1913 let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1914
1915 self.backups().maybe_trigger_backup();
1916
1917 Ok(ret)
1918 }
1919
1920 pub async fn room_keys_received_stream(
1951 &self,
1952 ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<>>
1953 {
1954 let olm = self.client.olm_machine().await;
1955 let olm = olm.as_ref()?;
1956
1957 Some(olm.store().room_keys_received_stream())
1958 }
1959
1960 pub async fn historic_room_key_stream(
1992 &self,
1993 ) -> Option<impl Stream<Item = RoomKeyBundleInfo> + use<>> {
1994 let olm = self.client.olm_machine().await;
1995 let olm = olm.as_ref()?;
1996
1997 Some(olm.store().historic_room_key_stream())
1998 }
1999
2000 pub fn secret_storage(&self) -> SecretStorage {
2002 SecretStorage { client: self.client.to_owned() }
2003 }
2004
2005 pub fn backups(&self) -> Backups {
2007 Backups { client: self.client.to_owned() }
2008 }
2009
2010 pub fn recovery(&self) -> Recovery {
2012 Recovery { client: self.client.to_owned() }
2013 }
2014
2015 pub fn dehydrated_devices(&self) -> dehydrated_devices::DehydratedDevices {
2022 dehydrated_devices::DehydratedDevices { client: self.client.to_owned() }
2023 }
2024
2025 pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
2036 if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
2038 let prev_holder = prev_lock.lock_holder();
2039 if prev_holder.is_some() && prev_holder.unwrap() == lock_value {
2040 return Ok(());
2041 }
2042 warn!(
2043 "Recreating cross-process store lock with a different holder value: \
2044 prev was {prev_holder:?}, new is {lock_value}"
2045 );
2046 }
2047
2048 let olm_machine = self.client.base_client().olm_machine().await;
2049 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
2050
2051 let lock = olm_machine.store().create_store_lock(
2052 "cross_process_lock".to_owned(),
2053 CrossProcessLockConfig::multi_process(lock_value.to_owned()),
2054 );
2055
2056 {
2062 let lock_result = lock.try_lock_once().await?;
2063
2064 if lock_result.is_ok() {
2065 olm_machine
2066 .initialize_crypto_store_generation(
2067 &self.client.locks().crypto_store_generation,
2068 )
2069 .await?;
2070 }
2071 }
2072
2073 self.client
2074 .locks()
2075 .cross_process_crypto_store_lock
2076 .set(lock)
2077 .map_err(|_| Error::BadCryptoStoreState)?;
2078
2079 Ok(())
2080 }
2081
2082 #[instrument(skip(self), fields(olm_machine_new_generation, olm_machine_generation))]
2087 async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
2088 let olm_machine_guard = self.client.olm_machine().await;
2089 if let Some(olm_machine) = olm_machine_guard.as_ref() {
2090 let (new_gen, generation_number) = olm_machine
2091 .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
2092 .await?;
2093
2094 Span::current()
2095 .record("olm_machine_new_generation", new_gen)
2096 .record("olm_machine_generation", generation_number);
2097 debug!("OlmMachine generation maintained in CryptoStore");
2098
2099 if new_gen {
2101 drop(olm_machine_guard);
2103 self.client.base_client().regenerate_olm(None).await?;
2105 }
2106 Ok(generation_number)
2107 } else {
2108 warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
2113 Ok(0)
2114 }
2115 }
2116
2117 pub async fn spin_lock_store(
2125 &self,
2126 max_backoff: Option<u32>,
2127 ) -> Result<Option<CrossProcessLockGuard>, Error> {
2128 self.lock_store(async move |lock| lock.spin_lock(max_backoff).await).await
2129 }
2130
2131 pub async fn try_lock_store_once(&self) -> Result<Option<CrossProcessLockGuard>, Error> {
2139 match self.lock_store(CrossProcessLock::try_lock_once).await {
2140 Err(Error::CrossProcessLockError(e))
2141 if matches!(*e, CrossProcessLockError::Unobtained(_)) =>
2142 {
2143 Ok(None)
2144 }
2145 other => other,
2146 }
2147 }
2148
2149 pub async fn lock_store<F: AcquireCrossProcessLockFn<LockableCryptoStore>>(
2156 &self,
2157 acquire: F,
2158 ) -> Result<Option<CrossProcessLockGuard>, Error> {
2159 let wrap_err = |e: CryptoStoreError| {
2160 Error::CrossProcessLockError(Box::new(CrossProcessLockError::TryLock(Arc::new(e))))
2161 };
2162 if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
2163 let guard = acquire(lock).await.map_err(wrap_err)??;
2164 let _ = self.on_lock_newly_acquired().await?;
2165 Ok(Some(guard.into_guard()))
2166 } else {
2167 Ok(None)
2168 }
2169 }
2170
2171 #[cfg(any(test, feature = "testing"))]
2173 pub async fn uploaded_key_count(&self) -> Result<u64> {
2174 let olm_machine = self.client.olm_machine().await;
2175 let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
2176 Ok(olm_machine.uploaded_key_count().await?)
2177 }
2178
2179 pub(crate) async fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
2204 let bundle_receiver_task = if self.client.inner.enable_share_history_on_invite {
2209 Some(BundleReceiverTask::new(&self.client).await)
2210 } else {
2211 None
2212 };
2213
2214 let mut tasks = self.client.inner.e2ee.tasks.lock();
2215
2216 let this = self.clone();
2217
2218 tasks.setup_e2ee = Some(spawn(
2219 async move {
2220 this.update_verification_state().await;
2223
2224 if this.settings().auto_enable_cross_signing
2225 && let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await
2226 {
2227 error!("Couldn't bootstrap cross signing {e:?}");
2228 }
2229
2230 if let Err(e) = this.backups().setup_and_resume().await {
2231 error!("Couldn't setup and resume backups {e:?}");
2232 }
2233 if let Err(e) = this.recovery().setup().await {
2234 error!("Couldn't setup and resume recovery {e:?}");
2235 }
2236 }
2237 .instrument(Span::current()),
2238 ));
2239
2240 tasks.receive_historic_room_key_bundles = bundle_receiver_task;
2241
2242 self.setup_room_membership_session_discard_handler();
2243 }
2244
2245 pub async fn wait_for_e2ee_initialization_tasks(&self) {
2248 let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
2249
2250 if let Some(task) = task
2251 && let Err(err) = task.await
2252 {
2253 warn!("Error when initializing backups: {err}");
2254 }
2255 }
2256
2257 pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
2267 let olm = self.client.olm_machine().await;
2268 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
2269
2270 if let Some((request_id, request)) = olm.upload_device_keys().await? {
2271 self.client.keys_upload(&request_id, &request).await?;
2272
2273 let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
2274 self.client.keys_query(&request_id, request.device_keys).await?;
2275 }
2276
2277 Ok(())
2278 }
2279
2280 pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
2281 self.recovery().update_state_after_keys_query(response).await;
2282
2283 if let Some(user_id) = self.client.user_id() {
2285 let contains_own_device = response.device_keys.contains_key(user_id);
2286
2287 if contains_own_device {
2288 self.update_verification_state().await;
2289 }
2290 }
2291 }
2292
2293 async fn update_verification_state(&self) {
2294 match self.get_own_device().await {
2295 Ok(device) => {
2296 if let Some(device) = device {
2297 let is_verified = device.is_cross_signed_by_owner();
2298
2299 if is_verified {
2300 self.client.inner.verification_state.set(VerificationState::Verified);
2301 } else if matches!(self.get_user_identity(device.user_id()).await, Ok(None)) {
2302 self.client.inner.verification_state.set(VerificationState::Unknown);
2306 } else {
2307 self.client.inner.verification_state.set(VerificationState::Unverified);
2308 }
2309 } else {
2310 warn!("Couldn't find out own device in the store.");
2311 self.client.inner.verification_state.set(VerificationState::Unknown);
2312 }
2313 }
2314 Err(error) => {
2315 warn!("Failed retrieving own device: {error}");
2316 self.client.inner.verification_state.set(VerificationState::Unknown);
2317 }
2318 }
2319 }
2320
2321 fn setup_room_membership_session_discard_handler(&self) {
2341 let client = self.client.downgrade();
2342 self.client.add_event_handler(|ev: OriginalSyncRoomMemberEvent, room: Room| async move {
2343 let Some(client) = client.get() else {
2344 return;
2346 };
2347 let Some(user_id) = client.user_id() else {
2348 return;
2350 };
2351 let olm = client.olm_machine().await;
2352 let Some(olm) = olm.as_ref() else {
2353 warn!("Cannot discard session - Olm machine is not available");
2354 return;
2355 };
2356
2357 if matches!(
2358 ev.membership_change(),
2359 MembershipChange::Joined |
2360 MembershipChange::Invited |
2361 MembershipChange::KnockAccepted |
2362 MembershipChange::InvitationAccepted |
2363 MembershipChange::ProfileChanged { .. }
2364 ) || ev.state_key == user_id {
2365 return;
2367 }
2368
2369 debug!(room_id = ?room.room_id(), member_id = ?ev.state_key, "Discarding session as a user left the room");
2370
2371 if let Err(e) = olm.discard_room_key(room.room_id()).await {
2375 warn!(
2376 room_id = ?room.room_id(),
2377 "Error discarding room key after member leave: {e:?}"
2378 );
2379 }
2380 });
2381 }
2382
2383 #[cfg(feature = "experimental-send-custom-to-device")]
2393 pub async fn encrypt_and_send_raw_to_device(
2394 &self,
2395 recipient_devices: Vec<&Device>,
2396 event_type: &str,
2397 content: Raw<AnyToDeviceEventContent>,
2398 share_strategy: CollectStrategy,
2399 ) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
2400 let users = recipient_devices.iter().map(|device| device.user_id());
2401
2402 self.client.claim_one_time_keys(users).await?;
2406
2407 let olm = self.client.olm_machine().await;
2408 let olm = olm.as_ref().expect("Olm machine wasn't started");
2409
2410 let (requests, withhelds) = olm
2411 .encrypt_content_for_devices(
2412 recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
2413 event_type,
2414 &content
2415 .deserialize_as::<serde_json::Value>()
2416 .expect("Deserialize as Value will always work"),
2417 share_strategy,
2418 )
2419 .await?;
2420
2421 let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
2422
2423 withhelds.iter().for_each(|(d, _)| {
2425 failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
2426 });
2427
2428 for request in requests {
2430 let ruma_request = RumaToDeviceRequest::new_raw(
2431 request.event_type.clone(),
2432 request.txn_id.clone(),
2433 request.messages.clone(),
2434 );
2435
2436 let send_result = self
2437 .client
2438 .send_inner(ruma_request, Some(RequestConfig::short_retry()), Default::default())
2439 .await;
2440
2441 if send_result.is_err() {
2444 for (user_id, device_map) in request.messages {
2446 for device_id in device_map.keys() {
2447 match device_id {
2448 DeviceIdOrAllDevices::DeviceId(device_id) => {
2449 failures.push((user_id.clone(), device_id.to_owned()));
2450 }
2451 DeviceIdOrAllDevices::AllDevices => {
2452 }
2454 }
2455 }
2456 }
2457 }
2458 }
2459
2460 Ok(failures)
2461 }
2462}
2463
2464#[cfg(all(test, not(target_family = "wasm")))]
2465mod tests {
2466 use std::{
2467 ops::Not,
2468 str::FromStr,
2469 sync::{
2470 Arc,
2471 atomic::{AtomicBool, Ordering},
2472 },
2473 };
2474
2475 use matrix_sdk_test::{
2476 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
2477 event_factory::EventFactory,
2478 };
2479 use ruma::{
2480 event_id,
2481 events::{reaction::ReactionEventContent, relation::Annotation},
2482 user_id,
2483 };
2484 use serde_json::json;
2485 use wiremock::{
2486 Mock, MockServer, Request, ResponseTemplate,
2487 matchers::{header, method, path_regex},
2488 };
2489
2490 use crate::{
2491 Client, assert_next_matches_with_timeout,
2492 config::RequestConfig,
2493 encryption::{
2494 DuplicateOneTimeKeyErrorMessage, OAuthCrossSigningResetInfo, VerificationState,
2495 },
2496 test_utils::{
2497 client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
2498 },
2499 };
2500
2501 #[async_test]
2502 async fn test_reaction_sending() {
2503 let server = MockServer::start().await;
2504 let client = logged_in_client(Some(server.uri())).await;
2505
2506 let event_id = event_id!("$2:example.org");
2507
2508 Mock::given(method("GET"))
2509 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
2510 .and(header("authorization", "Bearer 1234"))
2511 .respond_with(
2512 ResponseTemplate::new(200)
2513 .set_body_json(EventFactory::new().room_encryption().into_content()),
2514 )
2515 .mount(&server)
2516 .await;
2517
2518 Mock::given(method("PUT"))
2519 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
2520 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2521 "event_id": event_id,
2522 })))
2523 .mount(&server)
2524 .await;
2525
2526 let f = EventFactory::new().sender(user_id!("@example:localhost"));
2527 let response = SyncResponseBuilder::default()
2528 .add_joined_room(
2529 JoinedRoomBuilder::default()
2530 .add_state_event(
2531 f.member(user_id!("@example:localhost")).display_name("example"),
2532 )
2533 .add_state_event(f.power_levels())
2534 .add_state_event(f.room_encryption()),
2535 )
2536 .build_sync_response();
2537
2538 client.base_client().receive_sync_response(response).await.unwrap();
2539
2540 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
2541 assert!(
2542 room.latest_encryption_state().await.expect("Getting encryption state").is_encrypted()
2543 );
2544
2545 let event_id = event_id!("$1:example.org");
2546 let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
2547 room.send(reaction).await.expect("Sending the reaction should not fail");
2548
2549 room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
2550 }
2551
2552 #[cfg(feature = "sqlite")]
2553 #[async_test]
2554 async fn test_generation_counter_invalidates_olm_machine() {
2555 use matrix_sdk_base::store::RoomLoadSettings;
2558 let tmp_dir = tempfile::tempdir().unwrap();
2559 let sqlite_path = tmp_dir.path().join("generation_counter_sqlite.db");
2560 let session = mock_matrix_session();
2561
2562 let client1 = Client::builder()
2563 .homeserver_url("http://localhost:1234")
2564 .request_config(RequestConfig::new().disable_retry())
2565 .sqlite_store(&sqlite_path, None)
2566 .build()
2567 .await
2568 .unwrap();
2569 client1
2570 .matrix_auth()
2571 .restore_session(session.clone(), RoomLoadSettings::default())
2572 .await
2573 .unwrap();
2574
2575 let client2 = Client::builder()
2576 .homeserver_url("http://localhost:1234")
2577 .request_config(RequestConfig::new().disable_retry())
2578 .sqlite_store(sqlite_path, None)
2579 .build()
2580 .await
2581 .unwrap();
2582 client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
2583
2584 let guard = client1.encryption().try_lock_store_once().await.unwrap();
2587 assert!(guard.is_none());
2588
2589 client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2590 client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
2591
2592 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2594 assert!(acquired1.is_some());
2595
2596 let initial_olm_machine =
2599 client1.olm_machine().await.clone().expect("must have an olm machine");
2600
2601 let decryption_key = matrix_sdk_base::crypto::store::types::BackupDecryptionKey::new();
2604 let backup_key = decryption_key.megolm_v1_public_key();
2605 backup_key.set_version("1".to_owned());
2606 initial_olm_machine
2607 .backup_machine()
2608 .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
2609 .await
2610 .expect("Should save");
2611
2612 initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
2613
2614 assert!(client1.encryption().backups().are_enabled().await);
2615
2616 let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2618 assert!(acquired2.is_none());
2619
2620 drop(acquired1);
2622
2623 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2625 assert!(acquired1.is_some());
2626
2627 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2629 assert!(initial_olm_machine.same_as(&olm_machine));
2630
2631 drop(acquired1);
2633
2634 let acquired2 = client2.encryption().spin_lock_store(None).await.unwrap();
2636 assert!(acquired2.is_some());
2637
2638 drop(acquired2);
2640
2641 let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2643 assert!(acquired1.is_some());
2644
2645 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2647
2648 assert!(!initial_olm_machine.same_as(&olm_machine));
2649
2650 let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
2651 assert!(backup_key_new.decryption_key.is_some());
2652 assert_eq!(
2653 backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
2654 backup_key.to_base64()
2655 );
2656 assert!(client1.encryption().backups().are_enabled().await);
2657 }
2658
2659 #[cfg(feature = "sqlite")]
2660 #[async_test]
2661 async fn test_generation_counter_no_spurious_invalidation() {
2662 use matrix_sdk_base::store::RoomLoadSettings;
2665 let tmp_dir = tempfile::tempdir().unwrap();
2666 let sqlite_path = tmp_dir.path().join("generation_counter_no_spurious_invalidations.db");
2667 let session = mock_matrix_session();
2668
2669 let client = Client::builder()
2670 .homeserver_url("http://localhost:1234")
2671 .request_config(RequestConfig::new().disable_retry())
2672 .sqlite_store(&sqlite_path, None)
2673 .build()
2674 .await
2675 .unwrap();
2676 client
2677 .matrix_auth()
2678 .restore_session(session.clone(), RoomLoadSettings::default())
2679 .await
2680 .unwrap();
2681
2682 let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2683
2684 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2685
2686 let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2688 assert!(initial_olm_machine.same_as(&after_enabling_lock));
2689
2690 {
2691 let client2 = Client::builder()
2693 .homeserver_url("http://localhost:1234")
2694 .request_config(RequestConfig::new().disable_retry())
2695 .sqlite_store(sqlite_path, None)
2696 .build()
2697 .await
2698 .unwrap();
2699 client2
2700 .matrix_auth()
2701 .restore_session(session, RoomLoadSettings::default())
2702 .await
2703 .unwrap();
2704
2705 client2
2706 .encryption()
2707 .enable_cross_process_store_lock("client2".to_owned())
2708 .await
2709 .unwrap();
2710
2711 let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2712 assert!(guard.is_some());
2713
2714 drop(guard);
2715 }
2716
2717 {
2718 let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2719 assert!(acquired.is_some());
2720 }
2721
2722 let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2724 assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2725
2726 {
2727 let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2728 assert!(acquired.is_some());
2729 }
2730
2731 let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2733 assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2734 }
2735
2736 #[async_test]
2737 async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2738 let client = no_retry_test_client(None).await;
2740 let server = MockServer::start().await;
2741
2742 let mut verification_state = client.encryption().verification_state();
2744
2745 assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2747
2748 let keys_requested = Arc::new(AtomicBool::new(false));
2751 let inner_bool = keys_requested.clone();
2752
2753 Mock::given(method("GET"))
2754 .and(path_regex(
2755 r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2756 ))
2757 .respond_with(move |_req: &Request| {
2758 inner_bool.fetch_or(true, Ordering::SeqCst);
2759 ResponseTemplate::new(200).set_body_json(json!({}))
2760 })
2761 .mount(&server)
2762 .await;
2763
2764 set_client_session(&client).await;
2766
2767 assert!(keys_requested.load(Ordering::SeqCst).not());
2770 assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2771 }
2772
2773 #[test]
2774 fn test_oauth_reset_info_from_uiaa_info() {
2775 let auth_info = json!({
2776 "session": "dummy",
2777 "flows": [
2778 {
2779 "stages": [
2780 "org.matrix.cross_signing_reset"
2781 ]
2782 }
2783 ],
2784 "params": {
2785 "org.matrix.cross_signing_reset": {
2786 "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2787 }
2788 },
2789 "msg": "To reset..."
2790 });
2791
2792 let auth_info = serde_json::from_value(auth_info)
2793 .expect("We should be able to deserialize the UiaaInfo");
2794 OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2795 .expect("We should be able to fetch the cross-signing reset info from the auth info");
2796 }
2797
2798 #[test]
2799 fn test_duplicate_one_time_key_error_parsing() {
2800 let message = concat!(
2801 r#"One time key signed_curve25519:AAAAAAAAAAA already exists. "#,
2802 r#"Old key: {"key":"dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs","#,
2803 r#""signatures":{"@example:matrix.org":{"ed25519:AAAAAAAAAA":""#,
2804 r#"Fk45zHAbrd+1j9wZXLjL2Y/+DU/Mnz9yuvlfYBOOT7qExN2Jdud+5BAuNs8nZ/caS4wTF39Kg3zQpzaGERoCBg"}}};"#,
2805 r#" new key: {'key': 'CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno', "#,
2806 r#"'signatures': {'@example:matrix.org': {'ed25519:AAAAAAAAAA': "#,
2807 r#"'BQ9Gp0p+6srF+c8OyruqKKd9R4yaub3THYAyyBB/7X/rG8BwcAqFynzl1aGyFYun4Q+087a5OSiglCXI+/kQAA'}}}"#
2808 );
2809 let message = DuplicateOneTimeKeyErrorMessage::from_str(message)
2810 .expect("We should be able to parse the error message");
2811
2812 assert_eq!(message.old_key.to_base64(), "dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs");
2813 assert_eq!(message.new_key.to_base64(), "CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno");
2814
2815 DuplicateOneTimeKeyErrorMessage::from_str("One time key already exists.")
2816 .expect_err("We shouldn't be able to parse an incomplete error message");
2817 }
2818
2819 fn devices_to_verify_against_keys_query_response(
2823 devices: Vec<serde_json::Value>,
2824 ) -> serde_json::Value {
2825 let device_keys: serde_json::Map<String, serde_json::Value> = devices
2826 .into_iter()
2827 .map(|device| (device.get("device_id").unwrap().as_str().unwrap().to_owned(), device))
2828 .collect();
2829 json!({
2830 "device_keys": {
2831 "@example:localhost": device_keys,
2832 },
2833 "master_keys": {
2834 "@example:localhost": {
2835 "keys": {
2836 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU",
2837 },
2838 "usage": ["master"],
2839 "user_id": "@example:localhost",
2840 },
2841 },
2842 "self_signing_keys": {
2843 "@example:localhost": {
2844 "keys": {
2845 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM",
2846 },
2847 "usage": ["self_signing"],
2848 "user_id": "@example:localhost",
2849 "signatures": {
2850 "@example:localhost": {
2851 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "etO1bB+rCk+TQ/FcjQ8eWu/RsRNQNNQ1Ek+PD6//j8yz6igRjfvuHZaMvr/quAFrirfgExph2TdOwlDgN5bFCQ",
2852 },
2853 },
2854 },
2855 },
2856 "user_signing_keys": {
2857 "@example:localhost": {
2858 "keys": {
2859 "ed25519:CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0": "CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0",
2860 },
2861 "usage": ["user_signing"],
2862 "user_id": "@example:localhost",
2863 "signatures": {
2864 "@example:localhost": {
2865 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "E/DFi/hQTIb/7eSB+HbCXeTLFaLjqWHzLO9GwjL1qdhfO7ew4p6YdtXSH3T2YYr1dKCPteH/4nMYVwOhww2CBg",
2866 },
2867 },
2868 },
2869 }
2870 })
2871 }
2872
2873 #[async_test]
2876 async fn test_devices_to_verify_against_no_devices() {
2879 let server = MockServer::start().await;
2880 let client = logged_in_client(Some(server.uri())).await;
2881
2882 Mock::given(method("POST"))
2883 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2884 .respond_with(
2885 ResponseTemplate::new(200)
2886 .set_body_json(devices_to_verify_against_keys_query_response(vec![])),
2887 )
2888 .mount(&server)
2889 .await;
2890
2891 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2892 }
2893
2894 #[async_test]
2895 async fn test_devices_to_verify_against_cross_signed() {
2898 let server = MockServer::start().await;
2899 let client = logged_in_client(Some(server.uri())).await;
2900
2901 Mock::given(method("POST"))
2902 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2903 .respond_with(ResponseTemplate::new(200).set_body_json(
2904 devices_to_verify_against_keys_query_response(vec![
2905 json!({
2906 "algorithms": [
2907 "m.olm.v1.curve25519-aes-sha2",
2908 "m.megolm.v1.aes-sha2",
2909 ],
2910 "user_id": "@example:localhost",
2911 "device_id": "SIGNEDDEVICE",
2912 "keys": {
2913 "curve25519:SIGNEDDEVICE": "o1LqUtH/sqd3WF+BB2Qr77uw3sDmZhMOz68/IV9aHxs",
2914 "ed25519:SIGNEDDEVICE": "iVoEfMOoUqxXVMLdpZCOgvQuCrT3/kQWkBmB3Phi/lo",
2915 },
2916 "signatures": {
2917 "@example:localhost": {
2918 "ed25519:SIGNEDDEVICE": "C7yRu1fNrdD2EobVdtANMqk3LBtWtTRWrIU22xVS8/Om1kmA/luzek64R3N6JsZhYczVmZYBKhUC9kRvHHwOBg",
2919 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "frfh2HP28GclmGvwTic00Fj4nZCvm4RlRA6U56mnD5920hOi04+L055ojzp6ybZXvC/GQYfyTHwQXlUN1nvxBA",
2920 },
2921 },
2922 })
2923 ])
2924 ))
2925 .mount(&server)
2926 .await;
2927
2928 assert!(client.encryption().has_devices_to_verify_against().await.unwrap());
2929 }
2930
2931 #[async_test]
2932 async fn test_devices_to_verify_against_dehydrated_and_unsigned() {
2935 let server = MockServer::start().await;
2936 let client = logged_in_client(Some(server.uri())).await;
2937 let user_id = client.user_id().unwrap();
2938 let olm_machine = client.olm_machine().await;
2939 let olm_machine = olm_machine.as_ref().unwrap();
2940
2941 Mock::given(method("POST"))
2942 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2943 .respond_with(ResponseTemplate::new(200).set_body_json(
2944 devices_to_verify_against_keys_query_response(vec![
2945 json!({
2946 "algorithms": [
2947 "m.olm.v1.curve25519-aes-sha2",
2948 "m.megolm.v1.aes-sha2",
2949 ],
2950 "user_id": "@example:localhost",
2951 "device_id": "DEHYDRATEDDEVICE",
2952 "keys": {
2953 "curve25519:DEHYDRATEDDEVICE": "XOn5VguAgokZ3p9mBz2yOB395fn6j75G8jIPcXEWQGY",
2954 "ed25519:DEHYDRATEDDEVICE": "4GG5xmBT7z4rgUgmWNlKZ+ABE3QlGgTorF+luCnKfYI",
2955 },
2956 "dehydrated": true,
2957 "signatures": {
2958 "@example:localhost": {
2959 "ed25519:DEHYDRATEDDEVICE": "+OMasB7nzVlMV+zRDxkh4h8h/Q0bY42P1SPv7X2IURIelT5G+d+AYSmg30N4maphxEDBqt/vI8/lIr71exc3Dg",
2960 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "8DzynAgbYgXX1Md5d4Vw91Zstpoi4dpG7levFeVhi4psCAWuBnV76Qu1s2TGjQQ0CLDXEqcxxuX9X4eUK5TGCg",
2961 },
2962 },
2963 }),
2964 json!({
2965 "algorithms": [
2966 "m.olm.v1.curve25519-aes-sha2",
2967 "m.megolm.v1.aes-sha2",
2968 ],
2969 "user_id": "@example:localhost",
2970 "device_id": "UNSIGNEDDEVICE",
2971 "keys": {
2972 "curve25519:UNSIGNEDDEVICE": "mMby6NpprkHxj+ONfO9Z5lBqVUHJBMkrPFSNJhogBkg",
2973 "ed25519:UNSIGNEDDEVICE": "Zifq39ZDrlIaSRf0Hh22owEqXCPE+1JSSgs6LDlubwQ",
2974 },
2975 "signatures": {
2976 "@example:localhost": {
2977 "ed25519:UNSIGNEDDEVICE": "+L29RoDKoTufPGm/Bae65KHno7Z1H7GYhxSKpB4RQZRS7NrR29AMW1PVhEsIozYuDVEFuMZ0L8H3dlcaHxagBA",
2978 },
2979 },
2980 }),
2981 ])
2982 ))
2983 .mount(&server)
2984 .await;
2985
2986 let (request_id, request) = olm_machine.query_keys_for_users([user_id]);
2987 client.keys_query(&request_id, request.device_keys).await.unwrap();
2988
2989 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2990 }
2991
2992 #[cfg(feature = "experimental-send-custom-to-device")]
2993 mod resolve_recipient_devices {
2994 use matrix_sdk_test::async_test;
2995 use ruma::{
2996 OwnedDeviceId, device_id, owned_device_id, to_device::DeviceIdOrAllDevices, user_id,
2997 };
2998
2999 use super::super::Device;
3000 use crate::{Client, test_utils::mocks::MatrixMockServer};
3001
3002 const BOB_FIRST_DEVICE: &str = "B0B0B0B0B";
3003 const BOB_SECOND_DEVICE: &str = "B0B2B0B2";
3004 const BOB_THIRD_DEVICE: &str = "B0B3B0B3";
3005
3006 async fn alice_and_bob_with_two_bob_devices() -> (MatrixMockServer, Client, Client) {
3012 let server = MatrixMockServer::new().await;
3013 server.mock_crypto_endpoints_preset().await;
3014
3015 let (alice, bob) = server.set_up_alice_and_bob_for_encryption().await;
3016 assert_eq!(bob.device_id().unwrap(), device_id!(BOB_FIRST_DEVICE));
3017
3018 server
3019 .set_up_new_device_for_encryption(&bob, device_id!(BOB_SECOND_DEVICE), vec![&alice])
3020 .await;
3021
3022 server
3024 .mock_sync()
3025 .ok_and_run(&alice, |builder| {
3026 builder.add_change_device(bob.user_id().unwrap());
3027 })
3028 .await;
3029
3030 (server, alice, bob)
3031 }
3032
3033 fn device_ids(devices: Vec<Device>) -> Vec<OwnedDeviceId> {
3036 let mut device_ids: Vec<_> =
3037 devices.into_iter().map(|d| d.device_id().to_owned()).collect();
3038 device_ids.sort();
3039 device_ids
3040 }
3041
3042 #[async_test]
3043 async fn test_all_devices_resolves_to_every_known_device() {
3044 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3045
3046 let (devices, unknown_devices) = alice
3047 .resolve_recipient_devices(
3048 bob.user_id().unwrap(),
3049 vec![DeviceIdOrAllDevices::AllDevices],
3050 )
3051 .await
3052 .unwrap();
3053
3054 assert_eq!(
3055 device_ids(devices),
3056 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
3057 );
3058 assert!(unknown_devices.is_empty(), "`AllDevices` can't reference an unknown device");
3059 }
3060
3061 #[async_test]
3062 async fn test_explicit_device_ids_are_filtered_to_the_requested_ones() {
3063 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3064
3065 let (devices, unknown_devices) = alice
3066 .resolve_recipient_devices(
3067 bob.user_id().unwrap(),
3068 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_SECOND_DEVICE))],
3069 )
3070 .await
3071 .unwrap();
3072
3073 assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_SECOND_DEVICE)]);
3074 assert!(unknown_devices.is_empty());
3075 }
3076
3077 #[async_test]
3079 async fn test_several_explicit_device_ids_are_all_resolved() {
3080 let (server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3081
3082 server
3083 .set_up_new_device_for_encryption(&bob, device_id!(BOB_THIRD_DEVICE), vec![&alice])
3084 .await;
3085
3086 server
3088 .mock_sync()
3089 .ok_and_run(&alice, |builder| {
3090 builder.add_change_device(bob.user_id().unwrap());
3091 })
3092 .await;
3093
3094 let (devices, unknown_devices) = alice
3095 .resolve_recipient_devices(
3096 bob.user_id().unwrap(),
3097 vec![
3098 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3099 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_THIRD_DEVICE)),
3100 ],
3101 )
3102 .await
3103 .unwrap();
3104
3105 assert_eq!(
3106 device_ids(devices),
3107 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_THIRD_DEVICE)],
3108 "the second device was not requested and must be left out"
3109 );
3110 assert!(unknown_devices.is_empty());
3111 }
3112
3113 #[async_test]
3114 async fn test_unknown_device_is_reported_back() {
3115 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3116
3117 let (devices, unknown_devices) = alice
3118 .resolve_recipient_devices(
3119 bob.user_id().unwrap(),
3120 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE"))],
3121 )
3122 .await
3123 .unwrap();
3124
3125 assert!(devices.is_empty());
3126 assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3127 }
3128
3129 #[async_test]
3132 async fn test_known_and_unknown_devices_are_split() {
3133 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3134
3135 let (devices, unknown_devices) = alice
3136 .resolve_recipient_devices(
3137 bob.user_id().unwrap(),
3138 vec![
3139 DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3140 DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3141 ],
3142 )
3143 .await
3144 .unwrap();
3145
3146 assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3147 assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3148 }
3149
3150 #[async_test]
3154 async fn test_all_devices_takes_precedence_over_explicit_device_ids() {
3155 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3156
3157 let (devices, unknown_devices) = alice
3158 .resolve_recipient_devices(
3159 bob.user_id().unwrap(),
3160 vec![
3161 DeviceIdOrAllDevices::AllDevices,
3162 DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3163 ],
3164 )
3165 .await
3166 .unwrap();
3167
3168 assert_eq!(
3169 device_ids(devices),
3170 vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
3171 );
3172 assert!(unknown_devices.is_empty());
3173 }
3174
3175 #[async_test]
3178 async fn test_empty_recipient_list_resolves_to_no_device() {
3179 let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3180
3181 let (devices, unknown_devices) =
3182 alice.resolve_recipient_devices(bob.user_id().unwrap(), vec![]).await.unwrap();
3183
3184 assert!(devices.is_empty());
3185 assert!(unknown_devices.is_empty());
3186 }
3187
3188 #[async_test]
3191 async fn test_unknown_user_has_no_device() {
3192 let (_server, alice, _bob) = alice_and_bob_with_two_bob_devices().await;
3193 let unknown_user_id = user_id!("@carol:example.org");
3194
3195 let (devices, unknown_devices) = alice
3196 .resolve_recipient_devices(
3197 unknown_user_id,
3198 vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE))],
3199 )
3200 .await
3201 .unwrap();
3202
3203 assert!(devices.is_empty());
3204 assert_eq!(unknown_devices, vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3205
3206 let (devices, unknown_devices) = alice
3209 .resolve_recipient_devices(unknown_user_id, vec![DeviceIdOrAllDevices::AllDevices])
3210 .await
3211 .unwrap();
3212
3213 assert!(devices.is_empty());
3214 assert!(unknown_devices.is_empty());
3215 }
3216 }
3217}