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::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
31use eyeball::{SharedObservable, Subscriber};
32use futures_core::Stream;
33use futures_util::{
34 future::try_join,
35 stream::{self, StreamExt},
36};
37#[cfg(feature = "experimental-send-custom-to-device")]
38use matrix_sdk_base::crypto::CollectStrategy;
39use matrix_sdk_base::{
40 StateStoreDataKey, StateStoreDataValue,
41 cross_process_lock::{AcquireCrossProcessLockFn, CrossProcessLock, CrossProcessLockError},
42 crypto::{
43 CrossSigningBootstrapRequests, OlmMachine,
44 store::{
45 LockableCryptoStore, SecretImportError,
46 types::{RoomKeyBundleInfo, RoomKeyInfo},
47 },
48 types::{
49 SecretsBundle, SignedKey,
50 requests::{
51 OutgoingRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
52 },
53 },
54 },
55 sleep::sleep,
56 timeout::timeout,
57};
58use matrix_sdk_common::{executor::spawn, locks::Mutex as StdMutex};
59use ruma::{
60 DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, TransactionId, UserId,
61 api::{
62 client::{
63 keys::{
64 get_keys, upload_keys, upload_signatures::v3::Request as UploadSignaturesRequest,
65 upload_signing_keys::v3::Request as UploadSigningKeysRequest,
66 },
67 message::send_message_event,
68 to_device::send_event_to_device::v3::{
69 Request as RumaToDeviceRequest, Response as ToDeviceResponse,
70 },
71 uiaa::{AuthData, AuthType, OAuthParams, UiaaInfo},
72 },
73 error::{ErrorBody, StandardErrorBody},
74 },
75 assign,
76 events::room::{
77 MediaSource, ThumbnailInfo,
78 member::{MembershipChange, OriginalSyncRoomMemberEvent},
79 },
80};
81#[cfg(feature = "experimental-send-custom-to-device")]
82use ruma::{events::AnyToDeviceEventContent, serde::Raw, to_device::DeviceIdOrAllDevices};
83use serde::{Deserialize, de::Error as _};
84use tasks::BundleReceiverTask;
85use tokio::sync::{Mutex, RwLockReadGuard};
86use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
87use tracing::{Instrument, Span, debug, error, instrument, warn};
88use url::Url;
89use vodozemac::Curve25519PublicKey;
90
91use self::{
92 backups::{Backups, types::BackupClientState},
93 futures::UploadEncryptedFile,
94 identities::{Device, DeviceUpdates, IdentityUpdates, UserDevices, UserIdentity},
95 recovery::{Recovery, RecoveryState},
96 secret_storage::SecretStorage,
97 tasks::{BackupDownloadTask, BackupUploadingTask, ClientTasks},
98 verification::{SasVerification, Verification, VerificationRequest},
99};
100use crate::{
101 Client, Error, HttpError, Result, Room, TransmissionProgress,
102 attachment::Thumbnail,
103 client::{ClientInner, WeakClient},
104 cross_process_lock::CrossProcessLockGuard,
105 error::HttpResult,
106};
107
108pub mod backups;
109pub mod dehydrated_devices;
110pub mod futures;
111pub mod identities;
112pub mod recovery;
113pub mod secret_storage;
114pub(crate) mod tasks;
115pub mod verification;
116
117use matrix_sdk_base::crypto::OlmMachineBuilder;
118pub use matrix_sdk_base::crypto::{
119 CrossSigningStatus, CryptoStoreError, DecryptorError, EventError, KeyExportError, LocalTrust,
120 MediaEncryptionInfo, MegolmError, OlmError, RoomKeyImportResult, SessionCreationError,
121 SignatureError, VERSION,
122 olm::{
123 SessionCreationError as MegolmSessionCreationError,
124 SessionExportError as OlmSessionExportError,
125 },
126 vodozemac,
127};
128use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
129
130#[cfg(feature = "experimental-send-custom-to-device")]
131use crate::config::RequestConfig;
132pub use crate::error::RoomKeyImportError;
133
134#[cfg(feature = "sqlite")]
137#[derive(Debug, thiserror::Error)]
138pub enum BundleExportError {
139 #[error(transparent)]
141 OpenStoreError(#[from] matrix_sdk_sqlite::OpenStoreError),
142 #[error(transparent)]
144 StoreError(#[from] CryptoStoreError),
145 #[error(transparent)]
148 SecretExport(#[from] matrix_sdk_base::crypto::store::SecretsBundleExportError),
149}
150
151#[derive(Debug, thiserror::Error)]
154pub enum BundleImportError {
155 #[error(transparent)]
157 SecretImport(#[from] SecretImportError),
158 #[error(transparent)]
160 DeviceKeys(#[from] Error),
161}
162
163#[cfg(feature = "sqlite")]
172pub async fn export_secrets_bundle_from_store(
173 database_path: impl AsRef<Path>,
174 passphrase: Option<&str>,
175) -> std::result::Result<Option<(OwnedUserId, SecretsBundle)>, BundleExportError> {
176 use matrix_sdk_base::crypto::store::CryptoStore;
177
178 let store = matrix_sdk_sqlite::SqliteCryptoStore::open(database_path, passphrase).await?;
179 let account =
180 store.load_account().await.map_err(|e| BundleExportError::StoreError(e.into()))?;
181
182 if let Some(account) = account {
183 let machine = OlmMachineBuilder::new(&account.user_id, &account.device_id)
184 .with_crypto_store(store)
185 .build()
186 .await
187 .map_err(BundleExportError::StoreError)?;
188
189 let bundle = machine.store().export_secrets_bundle().await?;
190
191 Ok(Some((account.user_id.to_owned(), bundle)))
192 } else {
193 Ok(None)
194 }
195}
196
197pub(crate) struct EncryptionData {
199 pub tasks: StdMutex<ClientTasks>,
202
203 pub encryption_settings: EncryptionSettings,
205
206 pub backup_state: BackupClientState,
208
209 pub recovery_state: SharedObservable<RecoveryState>,
211
212 pub dehydrated_devices_state: dehydrated_devices::DehydratedDevicesState,
215}
216
217impl EncryptionData {
218 pub fn new(encryption_settings: EncryptionSettings) -> Self {
219 Self {
220 encryption_settings,
221
222 tasks: StdMutex::new(Default::default()),
223 backup_state: Default::default(),
224 recovery_state: Default::default(),
225 dehydrated_devices_state: Default::default(),
226 }
227 }
228
229 pub fn initialize_tasks(&self, client: &Arc<ClientInner>) {
230 let weak_client = WeakClient::from_inner(client);
231
232 let mut tasks = self.tasks.lock();
233 tasks.upload_room_keys = Some(BackupUploadingTask::new(weak_client.clone()));
234
235 if self.encryption_settings.backup_download_strategy
236 == BackupDownloadStrategy::AfterDecryptionFailure
237 {
238 tasks.download_room_keys = Some(BackupDownloadTask::new(weak_client));
239 }
240 }
241
242 pub fn initialize_recovery_state_update_task(&self, client: &Client) {
248 let mut guard = self.tasks.lock();
249
250 let future = Recovery::update_state_after_backup_state_change(client);
251 let join_handle = spawn(future);
252
253 guard.update_recovery_state_after_backup = Some(join_handle);
254 }
255}
256
257#[derive(Clone, Copy, Debug, Default)]
259pub struct EncryptionSettings {
260 pub auto_enable_cross_signing: bool,
266
267 pub backup_download_strategy: BackupDownloadStrategy,
272
273 pub auto_enable_backups: bool,
275}
276
277#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
279#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
280pub enum BackupDownloadStrategy {
281 OneShot,
292
293 AfterDecryptionFailure,
295
296 #[default]
301 Manual,
302}
303
304#[derive(Clone, Copy, Debug, Eq, PartialEq)]
309pub enum VerificationState {
310 Unknown,
312 Verified,
315 Unverified,
317}
318
319#[derive(Debug)]
330pub struct CrossSigningResetHandle {
331 client: Client,
332 upload_request: UploadSigningKeysRequest,
333 signatures_request: UploadSignaturesRequest,
334 auth_type: CrossSigningResetAuthType,
335 is_cancelled: Mutex<bool>,
336}
337
338impl CrossSigningResetHandle {
339 pub fn new(
341 client: Client,
342 upload_request: UploadSigningKeysRequest,
343 signatures_request: UploadSignaturesRequest,
344 auth_type: CrossSigningResetAuthType,
345 ) -> Self {
346 Self {
347 client,
348 upload_request,
349 signatures_request,
350 auth_type,
351 is_cancelled: Mutex::new(false),
352 }
353 }
354
355 pub fn auth_type(&self) -> &CrossSigningResetAuthType {
358 &self.auth_type
359 }
360
361 pub async fn auth(&self, auth: Option<AuthData>) -> Result<()> {
365 const RETRY_EVERY: Duration = Duration::from_millis(500);
367
368 const TIMEOUT: Duration = Duration::from_mins(2);
370
371 timeout(
372 async {
373 let mut upload_request = self.upload_request.clone();
374 upload_request.auth = auth;
375
376 debug!(
377 "Repeatedly PUTting to keys/device_signing/upload until it works \
378 or we hit a permanent failure."
379 );
380 while let Err(e) = self.client.send(upload_request.clone()).await {
381 if *self.is_cancelled.lock().await {
382 return Ok(());
383 }
384
385 match e.as_uiaa_response() {
386 Some(uiaa_info) => {
387 if !matches!(self.auth_type, CrossSigningResetAuthType::OAuth(_))
390 && uiaa_info.auth_error.is_some()
391 {
392 return Err(e.into());
393 }
394 }
395 None => return Err(e.into()),
396 }
397
398 debug!(
399 "PUT to keys/device_signing/upload failed with 401. Retrying after \
400 a short delay."
401 );
402 sleep(RETRY_EVERY).await;
403 }
404
405 self.client.send(self.signatures_request.clone()).await?;
406
407 Ok(())
408 },
409 TIMEOUT,
410 )
411 .await
412 .unwrap_or_else(|_| {
413 warn!("Timed out waiting for keys/device_signing/upload to succeed.");
414 Err(Error::Timeout)
415 })
416 }
417
418 pub async fn cancel(&self) {
420 *self.is_cancelled.lock().await = true;
421 }
422}
423
424#[derive(Debug, Clone)]
427pub enum CrossSigningResetAuthType {
428 Uiaa(UiaaInfo),
430 OAuth(OAuthCrossSigningResetInfo),
433}
434
435impl CrossSigningResetAuthType {
436 fn new(error: &HttpError) -> Result<Option<Self>> {
437 if let Some(auth_info) = error.as_uiaa_response() {
438 if let Ok(Some(auth_info)) = OAuthCrossSigningResetInfo::from_auth_info(auth_info) {
439 Ok(Some(CrossSigningResetAuthType::OAuth(auth_info)))
440 } else {
441 Ok(Some(CrossSigningResetAuthType::Uiaa(auth_info.clone())))
442 }
443 } else {
444 Ok(None)
445 }
446 }
447}
448
449#[derive(Debug, Clone, Deserialize)]
452pub struct OAuthCrossSigningResetInfo {
453 pub approval_url: Url,
455
456 pub session: Option<String>,
458}
459
460impl OAuthCrossSigningResetInfo {
461 fn from_auth_info(auth_info: &UiaaInfo) -> Result<Option<Self>> {
462 let Some(parameters) = auth_info.params::<OAuthParams>(&AuthType::OAuth)? else {
463 return Ok(None);
464 };
465
466 Ok(Some(OAuthCrossSigningResetInfo {
467 approval_url: parameters.url.as_str().try_into()?,
468 session: auth_info.session.clone(),
469 }))
470 }
471}
472
473#[derive(Clone, Debug)]
476pub struct DuplicateOneTimeKeyErrorMessage {
477 pub old_key: Curve25519PublicKey,
479 pub new_key: Curve25519PublicKey,
481}
482
483impl FromStr for DuplicateOneTimeKeyErrorMessage {
484 type Err = serde_json::Error;
485
486 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
487 let mut split = s.split_terminator(';');
491
492 let old_key = split
493 .next()
494 .ok_or(serde_json::Error::custom("Old key is missing in the error message"))?;
495 let new_key = split
496 .next()
497 .ok_or(serde_json::Error::custom("New key is missing in the error message"))?;
498
499 let old_key_index = old_key
502 .find("Old key:")
503 .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
504
505 let old_key = old_key[old_key_index..]
506 .trim()
507 .strip_prefix("Old key:")
508 .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
509
510 let new_key = new_key
513 .trim()
514 .strip_prefix("new key:")
515 .ok_or(serde_json::Error::custom("New key is missing the prefix"))?;
516
517 let new_key = new_key.replace("'", "\"");
520
521 let old_key: SignedKey = serde_json::from_str(old_key)?;
523 let new_key: SignedKey = serde_json::from_str(&new_key)?;
524
525 let old_key = old_key.key();
527 let new_key = new_key.key();
528
529 Ok(Self { old_key, new_key })
530 }
531}
532
533impl Client {
534 pub(crate) async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
535 self.base_client().olm_machine().await
536 }
537
538 pub(crate) async fn mark_request_as_sent(
539 &self,
540 request_id: &TransactionId,
541 response: impl Into<matrix_sdk_base::crypto::types::requests::AnyIncomingResponse<'_>>,
542 ) -> Result<(), matrix_sdk_base::Error> {
543 Ok(self
544 .olm_machine()
545 .await
546 .as_ref()
547 .expect(
548 "We should have an olm machine once we try to mark E2EE related requests as sent",
549 )
550 .mark_request_as_sent(request_id, response)
551 .await?)
552 }
553
554 #[instrument(skip(self, device_keys))]
560 pub(crate) async fn keys_query(
561 &self,
562 request_id: &TransactionId,
563 device_keys: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
564 ) -> Result<get_keys::v3::Response> {
565 let request = assign!(get_keys::v3::Request::new(), { device_keys });
566
567 let response = self.send(request).await?;
568 self.mark_request_as_sent(request_id, &response).await?;
569 self.encryption().update_state_after_keys_query(&response).await;
570
571 Ok(response)
572 }
573
574 pub fn upload_encrypted_file<'a, R: Read + ?Sized + 'a>(
608 &'a self,
609 reader: &'a mut R,
610 ) -> UploadEncryptedFile<'a, R> {
611 UploadEncryptedFile::new(self, reader)
612 }
613
614 pub(crate) async fn upload_encrypted_media_and_thumbnail(
617 &self,
618 data: &[u8],
619 thumbnail: Option<Thumbnail>,
620 send_progress: SharedObservable<TransmissionProgress>,
621 ) -> Result<(MediaSource, Option<(MediaSource, Box<ThumbnailInfo>)>)> {
622 let upload_thumbnail = self.upload_encrypted_thumbnail(thumbnail, send_progress.clone());
623
624 let upload_attachment = async {
625 let mut cursor = Cursor::new(data);
626 self.upload_encrypted_file(&mut cursor)
627 .with_send_progress_observable(send_progress)
628 .await
629 };
630
631 let (thumbnail, file) = try_join(upload_thumbnail, upload_attachment).await?;
632
633 Ok((MediaSource::Encrypted(Box::new(file)), thumbnail))
634 }
635
636 async fn upload_encrypted_thumbnail(
639 &self,
640 thumbnail: Option<Thumbnail>,
641 send_progress: SharedObservable<TransmissionProgress>,
642 ) -> Result<Option<(MediaSource, Box<ThumbnailInfo>)>> {
643 let Some(thumbnail) = thumbnail else {
644 return Ok(None);
645 };
646
647 let (data, _, thumbnail_info) = thumbnail.into_parts();
648 let mut cursor = Cursor::new(data);
649
650 let file = self
651 .upload_encrypted_file(&mut cursor)
652 .with_send_progress_observable(send_progress)
653 .await?;
654
655 Ok(Some((MediaSource::Encrypted(Box::new(file)), thumbnail_info)))
656 }
657
658 pub(crate) async fn claim_one_time_keys(
664 &self,
665 users: impl Iterator<Item = &UserId>,
666 ) -> Result<()> {
667 let _lock = self.locks().key_claim_lock.lock().await;
668
669 if let Some((request_id, request)) = self
670 .olm_machine()
671 .await
672 .as_ref()
673 .ok_or(Error::NoOlmMachine)?
674 .get_missing_sessions(users)
675 .await?
676 {
677 let response = self.send(request).await?;
678 self.mark_request_as_sent(&request_id, &response).await?;
679 }
680
681 Ok(())
682 }
683
684 #[instrument(skip(self, request))]
694 pub(crate) async fn keys_upload(
695 &self,
696 request_id: &TransactionId,
697 request: &upload_keys::v3::Request,
698 ) -> Result<upload_keys::v3::Response> {
699 debug!(
700 device_keys = request.device_keys.is_some(),
701 one_time_key_count = request.one_time_keys.len(),
702 "Uploading public encryption keys",
703 );
704
705 let response = self.send(request.clone()).await?;
706 self.mark_request_as_sent(request_id, &response).await?;
707
708 Ok(response)
709 }
710
711 pub(crate) async fn room_send_helper(
712 &self,
713 request: &RoomMessageRequest,
714 ) -> Result<send_message_event::v3::Response> {
715 let content = request.content.clone();
716 let txn_id = request.txn_id.clone();
717 let room_id = &request.room_id;
718
719 self.get_room(room_id)
720 .expect("Can't send a message to a room that isn't known to the store")
721 .send(*content)
722 .with_transaction_id(txn_id)
723 .await
724 .map(|result| result.response)
725 }
726
727 pub(crate) async fn send_to_device(
728 &self,
729 request: &ToDeviceRequest,
730 ) -> HttpResult<ToDeviceResponse> {
731 let request = RumaToDeviceRequest::new_raw(
732 request.event_type.clone(),
733 request.txn_id.clone(),
734 request.messages.clone(),
735 );
736
737 self.send(request).await
738 }
739
740 pub(crate) async fn send_verification_request(
741 &self,
742 request: OutgoingVerificationRequest,
743 ) -> Result<()> {
744 use matrix_sdk_base::crypto::types::requests::OutgoingVerificationRequest::*;
745
746 match request {
747 ToDevice(t) => {
748 self.send_to_device(&t).await?;
749 }
750 InRoom(r) => {
751 self.room_send_helper(&r).await?;
752 }
753 }
754
755 Ok(())
756 }
757
758 async fn send_outgoing_request(&self, r: OutgoingRequest) -> Result<()> {
759 use matrix_sdk_base::crypto::types::requests::AnyOutgoingRequest;
760
761 match r.request() {
762 AnyOutgoingRequest::KeysQuery(request) => {
763 self.keys_query(r.request_id(), request.device_keys.clone()).await?;
764 }
765 AnyOutgoingRequest::KeysUpload(request) => {
766 let response = self.keys_upload(r.request_id(), request).await;
767
768 if let Err(e) = &response {
769 match e.as_client_api_error() {
770 Some(e) if e.status_code == 400 => {
771 if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &e.body
772 {
773 {
778 let already_reported = self
779 .state_store()
780 .get_kv_data(StateStoreDataKey::OneTimeKeyAlreadyUploaded)
781 .await?
782 .is_some();
783
784 if message.starts_with("One time key") && !already_reported {
785 let error_message =
786 DuplicateOneTimeKeyErrorMessage::from_str(message);
787
788 if let Ok(message) = &error_message {
789 error!(
790 sentry = true,
791 old_key = %message.old_key,
792 new_key = %message.new_key,
793 "Duplicate one-time keys have been uploaded"
794 );
795 } else {
796 error!(
797 sentry = true,
798 "Duplicate one-time keys have been uploaded"
799 );
800 }
801
802 self.state_store()
803 .set_kv_data(
804 StateStoreDataKey::OneTimeKeyAlreadyUploaded,
805 StateStoreDataValue::OneTimeKeyAlreadyUploaded,
806 )
807 .await?;
808
809 if let Err(e) = self
810 .inner
811 .duplicate_key_upload_error_sender
812 .send(error_message.ok())
813 {
814 error!(
815 "Failed to dispatch duplicate key upload error notification: {}",
816 e
817 );
818 }
819 }
820 }
821 }
822 }
823 _ => {}
824 }
825
826 response?;
827 }
828 }
829 AnyOutgoingRequest::ToDeviceRequest(request) => {
830 let response = self.send_to_device(request).await?;
831 self.mark_request_as_sent(r.request_id(), &response).await?;
832 }
833 AnyOutgoingRequest::SignatureUpload(request) => {
834 let response = self.send(request.clone()).await?;
835 self.mark_request_as_sent(r.request_id(), &response).await?;
836 }
837 AnyOutgoingRequest::RoomMessage(request) => {
838 let response = self.room_send_helper(request).await?;
839 self.mark_request_as_sent(r.request_id(), &response).await?;
840 }
841 AnyOutgoingRequest::KeysClaim(request) => {
842 let response = self.send(request.clone()).await?;
843 self.mark_request_as_sent(r.request_id(), &response).await?;
844 }
845 }
846
847 Ok(())
848 }
849
850 #[instrument(skip_all)]
851 pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
852 const MAX_CONCURRENT_REQUESTS: usize = 20;
853
854 if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
857 warn!("Error while claiming one-time keys {:?}", e);
858 }
859
860 let outgoing_requests = stream::iter(
861 self.olm_machine()
862 .await
863 .as_ref()
864 .ok_or(Error::NoOlmMachine)?
865 .outgoing_requests()
866 .await?,
867 )
868 .map(|r| self.send_outgoing_request(r));
869
870 let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
871
872 requests
873 .for_each(|r| async move {
874 match r {
875 Ok(_) => (),
876 Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
877 }
878 })
879 .await;
880
881 Ok(())
882 }
883}
884
885#[cfg(any(feature = "testing", test))]
886impl Client {
887 pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
889 self.olm_machine().await
890 }
891
892 pub fn abort_bundle_receiver_task(&self) {
894 let tasks = self.inner.e2ee.tasks.lock();
895 if let Some(task) = tasks.receive_historic_room_key_bundles.as_ref() {
896 task.abort()
897 }
898 }
899}
900
901#[derive(Debug, Clone)]
905pub struct Encryption {
906 client: Client,
908}
909
910impl Encryption {
911 pub(crate) fn new(client: Client) -> Self {
912 Self { client }
913 }
914
915 pub(crate) fn settings(&self) -> EncryptionSettings {
917 self.client.inner.e2ee.encryption_settings
918 }
919
920 pub async fn ed25519_key(&self) -> Option<String> {
923 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
924 }
925
926 pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
928 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
929 }
930
931 pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
933 match self.get_own_device().await {
934 Ok(Some(device)) => device.first_time_seen_ts(),
935 _ => MilliSecondsSinceUnixEpoch::now(),
937 }
938 }
939
940 pub async fn import_secrets_bundle(
953 &self,
954 bundle: &SecretsBundle,
955 ) -> Result<(), BundleImportError> {
956 self.import_secrets_bundle_impl(bundle).await?;
957
958 self.ensure_device_keys_upload().await?;
961 self.wait_for_e2ee_initialization_tasks().await;
962
963 if !self.backups().are_enabled().await {
968 self.backups().maybe_resume_backups().await?;
969 }
970
971 Ok(())
972 }
973
974 pub(crate) async fn import_secrets_bundle_impl(
975 &self,
976 bundle: &SecretsBundle,
977 ) -> Result<(), SecretImportError> {
978 let olm_machine = self.client.olm_machine().await;
979 let olm_machine =
980 olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
981
982 olm_machine.store().import_secrets_bundle(bundle).await
983 }
984
985 pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
990 let olm = self.client.olm_machine().await;
991 let machine = olm.as_ref()?;
992 Some(machine.cross_signing_status().await)
993 }
994
995 pub async fn has_devices_to_verify_against(&self) -> Result<bool> {
1001 let olm_machine = self.client.olm_machine().await;
1002 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1003 let user_id = olm_machine.user_id();
1004
1005 self.ensure_initial_key_query().await?;
1006
1007 let devices = self.get_user_devices(user_id).await?;
1008
1009 let ret = devices.devices().any(|device| {
1010 device.is_cross_signed_by_owner()
1011 && device.curve25519_key().is_some()
1012 && !device.is_dehydrated()
1013 });
1014
1015 Ok(ret)
1016 }
1017
1018 pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
1023 if let Some(machine) = self.client.olm_machine().await.as_ref() {
1024 machine.tracked_users().await
1025 } else {
1026 Ok(HashSet::new())
1027 }
1028 }
1029
1030 pub fn verification_state(&self) -> Subscriber<VerificationState> {
1053 self.client.inner.verification_state.subscribe_reset()
1054 }
1055
1056 pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
1058 let olm = self.client.olm_machine().await;
1059 let olm = olm.as_ref()?;
1060 #[allow(clippy::bind_instead_of_map)]
1061 olm.get_verification(user_id, flow_id).and_then(|v| match v {
1062 matrix_sdk_base::crypto::Verification::SasV1(sas) => {
1063 Some(SasVerification { inner: sas, client: self.client.clone() }.into())
1064 }
1065 #[cfg(feature = "qrcode")]
1066 matrix_sdk_base::crypto::Verification::QrV1(qr) => {
1067 Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
1068 }
1069 _ => None,
1070 })
1071 }
1072
1073 pub async fn get_verification_request(
1076 &self,
1077 user_id: &UserId,
1078 flow_id: impl AsRef<str>,
1079 ) -> Option<VerificationRequest> {
1080 let olm = self.client.olm_machine().await;
1081 let olm = olm.as_ref()?;
1082
1083 olm.get_verification_request(user_id, flow_id)
1084 .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
1085 }
1086
1087 pub async fn get_device(
1121 &self,
1122 user_id: &UserId,
1123 device_id: &DeviceId,
1124 ) -> Result<Option<Device>, CryptoStoreError> {
1125 let olm = self.client.olm_machine().await;
1126 let Some(machine) = olm.as_ref() else { return Ok(None) };
1127 let device = machine.get_device(user_id, device_id, None).await?;
1128 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1129 }
1130
1131 pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
1138 let olm = self.client.olm_machine().await;
1139 let Some(machine) = olm.as_ref() else { return Ok(None) };
1140 let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
1141 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1142 }
1143
1144 pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
1170 let devices = self
1171 .client
1172 .olm_machine()
1173 .await
1174 .as_ref()
1175 .ok_or(Error::NoOlmMachine)?
1176 .get_user_devices(user_id, None)
1177 .await?;
1178
1179 Ok(UserDevices { inner: devices, client: self.client.clone() })
1180 }
1181
1182 pub async fn get_user_identity(
1218 &self,
1219 user_id: &UserId,
1220 ) -> Result<Option<UserIdentity>, CryptoStoreError> {
1221 let olm = self.client.olm_machine().await;
1222 let Some(olm) = olm.as_ref() else { return Ok(None) };
1223 let identity = olm.get_identity(user_id, None).await?;
1224
1225 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1226 }
1227
1228 pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
1266 let olm = self.client.olm_machine().await;
1267 let Some(olm) = olm.as_ref() else { return Ok(None) };
1268
1269 let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
1270 self.client.keys_query(&request_id, request.device_keys).await?;
1271
1272 let identity = olm.get_identity(user_id, None).await?;
1273 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1274 }
1275
1276 pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates> + use<>> {
1307 let olm = self.client.olm_machine().await;
1308 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1309 let client = self.client.to_owned();
1310
1311 Ok(olm
1312 .store()
1313 .devices_stream()
1314 .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1315 }
1316
1317 pub async fn user_identities_stream(
1345 &self,
1346 ) -> Result<impl Stream<Item = IdentityUpdates> + use<>> {
1347 let olm = self.client.olm_machine().await;
1348 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1349 let client = self.client.to_owned();
1350
1351 Ok(olm
1352 .store()
1353 .user_identities_stream()
1354 .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1355 }
1356
1357 pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1396 let olm = self.client.olm_machine().await;
1397 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1398
1399 let CrossSigningBootstrapRequests {
1400 upload_signing_keys_req,
1401 upload_keys_req,
1402 upload_signatures_req,
1403 } = olm.bootstrap_cross_signing(false).await?;
1404
1405 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1406 auth: auth_data,
1407 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1408 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1409 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1410 });
1411
1412 if let Some(req) = upload_keys_req {
1413 self.client.send_outgoing_request(req).await?;
1414 }
1415 self.client.send(upload_signing_keys_req).await?;
1416 self.client.send(upload_signatures_req).await?;
1417
1418 Ok(())
1419 }
1420
1421 pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1462 let olm = self.client.olm_machine().await;
1463 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1464
1465 let CrossSigningBootstrapRequests {
1466 upload_keys_req,
1467 upload_signing_keys_req,
1468 upload_signatures_req,
1469 } = olm.bootstrap_cross_signing(true).await?;
1470
1471 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1472 auth: None,
1473 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1474 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1475 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1476 });
1477
1478 if let Some(req) = upload_keys_req {
1479 self.client.send_outgoing_request(req).await?;
1480 }
1481
1482 if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1483 if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1484 let client = self.client.clone();
1485
1486 Ok(Some(CrossSigningResetHandle::new(
1487 client,
1488 upload_signing_keys_req,
1489 upload_signatures_req,
1490 auth_type,
1491 )))
1492 } else {
1493 Err(error.into())
1494 }
1495 } else {
1496 self.client.send(upload_signatures_req).await?;
1497
1498 Ok(None)
1499 }
1500 }
1501
1502 async fn ensure_initial_key_query(&self) -> Result<()> {
1505 let olm_machine = self.client.olm_machine().await;
1506 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1507
1508 let user_id = olm_machine.user_id();
1509
1510 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1511 let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1512 self.client.keys_query(&request_id, request.device_keys).await?;
1513 }
1514
1515 Ok(())
1516 }
1517
1518 pub async fn bootstrap_cross_signing_if_needed(
1565 &self,
1566 auth_data: Option<AuthData>,
1567 ) -> Result<()> {
1568 let olm_machine = self.client.olm_machine().await;
1569 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1570 let user_id = olm_machine.user_id();
1571
1572 self.ensure_initial_key_query().await?;
1573
1574 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1575 self.bootstrap_cross_signing(auth_data).await?;
1576 }
1577
1578 Ok(())
1579 }
1580
1581 #[cfg(not(target_family = "wasm"))]
1633 pub async fn export_room_keys(
1634 &self,
1635 path: PathBuf,
1636 passphrase: &str,
1637 predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1638 ) -> Result<()> {
1639 let olm = self.client.olm_machine().await;
1640 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1641
1642 let keys = olm.store().export_room_keys(predicate).await?;
1643 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1644
1645 let encrypt = move || -> Result<()> {
1646 let export: String =
1647 matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1648 let mut file = std::fs::File::create(path)?;
1649 file.write_all(&export.into_bytes())?;
1650 Ok(())
1651 };
1652
1653 let task = tokio::task::spawn_blocking(encrypt);
1654 task.await.expect("Task join error")
1655 }
1656
1657 #[cfg(not(target_family = "wasm"))]
1695 pub async fn import_room_keys(
1696 &self,
1697 path: PathBuf,
1698 passphrase: &str,
1699 ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1700 let olm = self.client.olm_machine().await;
1701 let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1702 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1703
1704 let decrypt = move || {
1705 let file = std::fs::File::open(path)?;
1706 matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1707 };
1708
1709 let task = tokio::task::spawn_blocking(decrypt);
1710 let import = task.await.expect("Task join error")?;
1711
1712 let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1713
1714 self.backups().maybe_trigger_backup();
1715
1716 Ok(ret)
1717 }
1718
1719 pub async fn room_keys_received_stream(
1750 &self,
1751 ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<>>
1752 {
1753 let olm = self.client.olm_machine().await;
1754 let olm = olm.as_ref()?;
1755
1756 Some(olm.store().room_keys_received_stream())
1757 }
1758
1759 pub async fn historic_room_key_stream(
1790 &self,
1791 ) -> Option<impl Stream<Item = RoomKeyBundleInfo> + use<>> {
1792 let olm = self.client.olm_machine().await;
1793 let olm = olm.as_ref()?;
1794
1795 Some(olm.store().historic_room_key_stream())
1796 }
1797
1798 pub fn secret_storage(&self) -> SecretStorage {
1800 SecretStorage { client: self.client.to_owned() }
1801 }
1802
1803 pub fn backups(&self) -> Backups {
1805 Backups { client: self.client.to_owned() }
1806 }
1807
1808 pub fn recovery(&self) -> Recovery {
1810 Recovery { client: self.client.to_owned() }
1811 }
1812
1813 pub fn dehydrated_devices(&self) -> dehydrated_devices::DehydratedDevices {
1821 dehydrated_devices::DehydratedDevices { client: self.client.to_owned() }
1822 }
1823
1824 pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
1836 if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1838 let prev_holder = prev_lock.lock_holder();
1839 if prev_holder.is_some() && prev_holder.unwrap() == lock_value {
1840 return Ok(());
1841 }
1842 warn!(
1843 "Recreating cross-process store lock with a different holder value: \
1844 prev was {prev_holder:?}, new is {lock_value}"
1845 );
1846 }
1847
1848 let olm_machine = self.client.base_client().olm_machine().await;
1849 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1850
1851 let lock = olm_machine.store().create_store_lock(
1852 "cross_process_lock".to_owned(),
1853 CrossProcessLockConfig::multi_process(lock_value.to_owned()),
1854 );
1855
1856 {
1861 let lock_result = lock.try_lock_once().await?;
1862
1863 if lock_result.is_ok() {
1864 olm_machine
1865 .initialize_crypto_store_generation(
1866 &self.client.locks().crypto_store_generation,
1867 )
1868 .await?;
1869 }
1870 }
1871
1872 self.client
1873 .locks()
1874 .cross_process_crypto_store_lock
1875 .set(lock)
1876 .map_err(|_| Error::BadCryptoStoreState)?;
1877
1878 Ok(())
1879 }
1880
1881 #[instrument(skip(self), fields(olm_machine_new_generation, olm_machine_generation))]
1886 async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
1887 let olm_machine_guard = self.client.olm_machine().await;
1888 if let Some(olm_machine) = olm_machine_guard.as_ref() {
1889 let (new_gen, generation_number) = olm_machine
1890 .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
1891 .await?;
1892
1893 Span::current()
1894 .record("olm_machine_new_generation", new_gen)
1895 .record("olm_machine_generation", generation_number);
1896 debug!("OlmMachine generation maintained in CryptoStore");
1897
1898 if new_gen {
1900 drop(olm_machine_guard);
1902 self.client.base_client().regenerate_olm(None).await?;
1904 }
1905 Ok(generation_number)
1906 } else {
1907 warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
1912 Ok(0)
1913 }
1914 }
1915
1916 pub async fn spin_lock_store(
1924 &self,
1925 max_backoff: Option<u32>,
1926 ) -> Result<Option<CrossProcessLockGuard>, Error> {
1927 self.lock_store(async move |lock| lock.spin_lock(max_backoff).await).await
1928 }
1929
1930 pub async fn try_lock_store_once(&self) -> Result<Option<CrossProcessLockGuard>, Error> {
1938 match self.lock_store(CrossProcessLock::try_lock_once).await {
1939 Err(Error::CrossProcessLockError(e))
1940 if matches!(*e, CrossProcessLockError::Unobtained(_)) =>
1941 {
1942 Ok(None)
1943 }
1944 other => other,
1945 }
1946 }
1947
1948 pub async fn lock_store<F: AcquireCrossProcessLockFn<LockableCryptoStore>>(
1955 &self,
1956 acquire: F,
1957 ) -> Result<Option<CrossProcessLockGuard>, Error> {
1958 let wrap_err = |e: CryptoStoreError| {
1959 Error::CrossProcessLockError(Box::new(CrossProcessLockError::TryLock(Arc::new(e))))
1960 };
1961 if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1962 let guard = acquire(lock).await.map_err(wrap_err)??;
1963 let _ = self.on_lock_newly_acquired().await?;
1964 Ok(Some(guard.into_guard()))
1965 } else {
1966 Ok(None)
1967 }
1968 }
1969
1970 #[cfg(any(test, feature = "testing"))]
1972 pub async fn uploaded_key_count(&self) -> Result<u64> {
1973 let olm_machine = self.client.olm_machine().await;
1974 let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
1975 Ok(olm_machine.uploaded_key_count().await?)
1976 }
1977
1978 pub(crate) async fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
2002 let bundle_receiver_task = if self.client.inner.enable_share_history_on_invite {
2006 Some(BundleReceiverTask::new(&self.client).await)
2007 } else {
2008 None
2009 };
2010
2011 let mut tasks = self.client.inner.e2ee.tasks.lock();
2012
2013 let this = self.clone();
2014
2015 tasks.setup_e2ee = Some(spawn(
2016 async move {
2017 this.update_verification_state().await;
2020
2021 if this.settings().auto_enable_cross_signing
2022 && let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await
2023 {
2024 error!("Couldn't bootstrap cross signing {e:?}");
2025 }
2026
2027 if let Err(e) = this.backups().setup_and_resume().await {
2028 error!("Couldn't setup and resume backups {e:?}");
2029 }
2030 if let Err(e) = this.recovery().setup().await {
2031 error!("Couldn't setup and resume recovery {e:?}");
2032 }
2033 }
2034 .instrument(Span::current()),
2035 ));
2036
2037 tasks.receive_historic_room_key_bundles = bundle_receiver_task;
2038
2039 self.setup_room_membership_session_discard_handler();
2040 }
2041
2042 pub async fn wait_for_e2ee_initialization_tasks(&self) {
2045 let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
2046
2047 if let Some(task) = task
2048 && let Err(err) = task.await
2049 {
2050 warn!("Error when initializing backups: {err}");
2051 }
2052 }
2053
2054 pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
2064 let olm = self.client.olm_machine().await;
2065 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
2066
2067 if let Some((request_id, request)) = olm.upload_device_keys().await? {
2068 self.client.keys_upload(&request_id, &request).await?;
2069
2070 let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
2071 self.client.keys_query(&request_id, request.device_keys).await?;
2072 }
2073
2074 Ok(())
2075 }
2076
2077 pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
2078 self.recovery().update_state_after_keys_query(response).await;
2079
2080 if let Some(user_id) = self.client.user_id() {
2082 let contains_own_device = response.device_keys.contains_key(user_id);
2083
2084 if contains_own_device {
2085 self.update_verification_state().await;
2086 }
2087 }
2088 }
2089
2090 async fn update_verification_state(&self) {
2091 match self.get_own_device().await {
2092 Ok(device) => {
2093 if let Some(device) = device {
2094 let is_verified = device.is_cross_signed_by_owner();
2095
2096 if is_verified {
2097 self.client.inner.verification_state.set(VerificationState::Verified);
2098 } else {
2099 self.client.inner.verification_state.set(VerificationState::Unverified);
2100 }
2101 } else {
2102 warn!("Couldn't find out own device in the store.");
2103 self.client.inner.verification_state.set(VerificationState::Unknown);
2104 }
2105 }
2106 Err(error) => {
2107 warn!("Failed retrieving own device: {error}");
2108 self.client.inner.verification_state.set(VerificationState::Unknown);
2109 }
2110 }
2111 }
2112
2113 fn setup_room_membership_session_discard_handler(&self) {
2133 let client = WeakClient::from_client(&self.client);
2134 self.client.add_event_handler(|ev: OriginalSyncRoomMemberEvent, room: Room| async move {
2135 let Some(client) = client.get() else {
2136 return;
2138 };
2139 let Some(user_id) = client.user_id() else {
2140 return;
2142 };
2143 let olm = client.olm_machine().await;
2144 let Some(olm) = olm.as_ref() else {
2145 warn!("Cannot discard session - Olm machine is not available");
2146 return;
2147 };
2148
2149 if matches!(
2150 ev.membership_change(),
2151 MembershipChange::Joined |
2152 MembershipChange::Invited |
2153 MembershipChange::KnockAccepted |
2154 MembershipChange::InvitationAccepted |
2155 MembershipChange::ProfileChanged { .. }
2156 ) || ev.sender == user_id {
2157 return;
2159 }
2160
2161 debug!(room_id = ?room.room_id(), member_id = ?ev.sender, "Discarding session as a user left the room");
2162
2163 if let Err(e) = olm.discard_room_key(room.room_id()).await {
2166 warn!(
2167 room_id = ?room.room_id(),
2168 "Error discarding room key after member leave: {e:?}"
2169 );
2170 }
2171 });
2172 }
2173
2174 #[cfg(feature = "experimental-send-custom-to-device")]
2183 pub async fn encrypt_and_send_raw_to_device(
2184 &self,
2185 recipient_devices: Vec<&Device>,
2186 event_type: &str,
2187 content: Raw<AnyToDeviceEventContent>,
2188 share_strategy: CollectStrategy,
2189 ) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
2190 let users = recipient_devices.iter().map(|device| device.user_id());
2191
2192 self.client.claim_one_time_keys(users).await?;
2196
2197 let olm = self.client.olm_machine().await;
2198 let olm = olm.as_ref().expect("Olm machine wasn't started");
2199
2200 let (requests, withhelds) = olm
2201 .encrypt_content_for_devices(
2202 recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
2203 event_type,
2204 &content
2205 .deserialize_as::<serde_json::Value>()
2206 .expect("Deserialize as Value will always work"),
2207 share_strategy,
2208 )
2209 .await?;
2210
2211 let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
2212
2213 withhelds.iter().for_each(|(d, _)| {
2215 failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
2216 });
2217
2218 for request in requests {
2220 let ruma_request = RumaToDeviceRequest::new_raw(
2221 request.event_type.clone(),
2222 request.txn_id.clone(),
2223 request.messages.clone(),
2224 );
2225
2226 let send_result = self
2227 .client
2228 .send_inner(ruma_request, Some(RequestConfig::short_retry()), Default::default())
2229 .await;
2230
2231 if send_result.is_err() {
2233 for (user_id, device_map) in request.messages {
2235 for device_id in device_map.keys() {
2236 match device_id {
2237 DeviceIdOrAllDevices::DeviceId(device_id) => {
2238 failures.push((user_id.clone(), device_id.to_owned()));
2239 }
2240 DeviceIdOrAllDevices::AllDevices => {
2241 }
2243 }
2244 }
2245 }
2246 }
2247 }
2248
2249 Ok(failures)
2250 }
2251}
2252
2253#[cfg(all(test, not(target_family = "wasm")))]
2254mod tests {
2255 use std::{
2256 ops::Not,
2257 str::FromStr,
2258 sync::{
2259 Arc,
2260 atomic::{AtomicBool, Ordering},
2261 },
2262 time::Duration,
2263 };
2264
2265 use matrix_sdk_test::{
2266 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
2267 event_factory::EventFactory,
2268 };
2269 use ruma::{
2270 event_id,
2271 events::{reaction::ReactionEventContent, relation::Annotation},
2272 user_id,
2273 };
2274 use serde_json::json;
2275 use wiremock::{
2276 Mock, MockServer, Request, ResponseTemplate,
2277 matchers::{header, method, path_regex},
2278 };
2279
2280 use crate::{
2281 Client, assert_next_matches_with_timeout,
2282 config::RequestConfig,
2283 encryption::{
2284 DuplicateOneTimeKeyErrorMessage, OAuthCrossSigningResetInfo, VerificationState,
2285 },
2286 test_utils::{
2287 client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
2288 },
2289 };
2290
2291 #[async_test]
2292 async fn test_reaction_sending() {
2293 let server = MockServer::start().await;
2294 let client = logged_in_client(Some(server.uri())).await;
2295
2296 let event_id = event_id!("$2:example.org");
2297
2298 Mock::given(method("GET"))
2299 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
2300 .and(header("authorization", "Bearer 1234"))
2301 .respond_with(
2302 ResponseTemplate::new(200)
2303 .set_body_json(EventFactory::new().room_encryption().into_content()),
2304 )
2305 .mount(&server)
2306 .await;
2307
2308 Mock::given(method("PUT"))
2309 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
2310 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2311 "event_id": event_id,
2312 })))
2313 .mount(&server)
2314 .await;
2315
2316 let f = EventFactory::new().sender(user_id!("@example:localhost"));
2317 let response = SyncResponseBuilder::default()
2318 .add_joined_room(
2319 JoinedRoomBuilder::default()
2320 .add_state_event(
2321 f.member(user_id!("@example:localhost")).display_name("example"),
2322 )
2323 .add_state_event(f.default_power_levels())
2324 .add_state_event(f.room_encryption()),
2325 )
2326 .build_sync_response();
2327
2328 client.base_client().receive_sync_response(response).await.unwrap();
2329
2330 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
2331 assert!(
2332 room.latest_encryption_state().await.expect("Getting encryption state").is_encrypted()
2333 );
2334
2335 let event_id = event_id!("$1:example.org");
2336 let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
2337 room.send(reaction).await.expect("Sending the reaction should not fail");
2338
2339 room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
2340 }
2341
2342 #[cfg(feature = "sqlite")]
2343 #[async_test]
2344 async fn test_generation_counter_invalidates_olm_machine() {
2345 use matrix_sdk_base::store::RoomLoadSettings;
2348 let sqlite_path = std::env::temp_dir().join("generation_counter_sqlite.db");
2349 let session = mock_matrix_session();
2350
2351 let client1 = Client::builder()
2352 .homeserver_url("http://localhost:1234")
2353 .request_config(RequestConfig::new().disable_retry())
2354 .sqlite_store(&sqlite_path, None)
2355 .build()
2356 .await
2357 .unwrap();
2358 client1
2359 .matrix_auth()
2360 .restore_session(session.clone(), RoomLoadSettings::default())
2361 .await
2362 .unwrap();
2363
2364 let client2 = Client::builder()
2365 .homeserver_url("http://localhost:1234")
2366 .request_config(RequestConfig::new().disable_retry())
2367 .sqlite_store(sqlite_path, None)
2368 .build()
2369 .await
2370 .unwrap();
2371 client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
2372
2373 let guard = client1.encryption().try_lock_store_once().await.unwrap();
2375 assert!(guard.is_none());
2376
2377 client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2378 client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
2379
2380 let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2382 assert!(acquired1.is_some());
2383
2384 let initial_olm_machine =
2386 client1.olm_machine().await.clone().expect("must have an olm machine");
2387
2388 let decryption_key = matrix_sdk_base::crypto::store::types::BackupDecryptionKey::new();
2390 let backup_key = decryption_key.megolm_v1_public_key();
2391 backup_key.set_version("1".to_owned());
2392 initial_olm_machine
2393 .backup_machine()
2394 .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
2395 .await
2396 .expect("Should save");
2397
2398 initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
2399
2400 assert!(client1.encryption().backups().are_enabled().await);
2401
2402 let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2404 assert!(acquired2.is_none());
2405
2406 drop(acquired1);
2408 tokio::time::sleep(Duration::from_millis(100)).await;
2409
2410 let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2412 assert!(acquired1.is_some());
2413
2414 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2416 assert!(initial_olm_machine.same_as(&olm_machine));
2417
2418 drop(acquired1);
2420 tokio::time::sleep(Duration::from_millis(100)).await;
2421
2422 let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2424 assert!(acquired2.is_some());
2425
2426 drop(acquired2);
2428 tokio::time::sleep(Duration::from_millis(100)).await;
2429
2430 let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2432 assert!(acquired1.is_some());
2433
2434 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2436
2437 assert!(!initial_olm_machine.same_as(&olm_machine));
2438
2439 let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
2440 assert!(backup_key_new.decryption_key.is_some());
2441 assert_eq!(
2442 backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
2443 backup_key.to_base64()
2444 );
2445 assert!(client1.encryption().backups().are_enabled().await);
2446 }
2447
2448 #[cfg(feature = "sqlite")]
2449 #[async_test]
2450 async fn test_generation_counter_no_spurious_invalidation() {
2451 use matrix_sdk_base::store::RoomLoadSettings;
2454 let sqlite_path =
2455 std::env::temp_dir().join("generation_counter_no_spurious_invalidations.db");
2456 let session = mock_matrix_session();
2457
2458 let client = Client::builder()
2459 .homeserver_url("http://localhost:1234")
2460 .request_config(RequestConfig::new().disable_retry())
2461 .sqlite_store(&sqlite_path, None)
2462 .build()
2463 .await
2464 .unwrap();
2465 client
2466 .matrix_auth()
2467 .restore_session(session.clone(), RoomLoadSettings::default())
2468 .await
2469 .unwrap();
2470
2471 let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2472
2473 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2474
2475 let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2477 assert!(initial_olm_machine.same_as(&after_enabling_lock));
2478
2479 {
2480 let client2 = Client::builder()
2482 .homeserver_url("http://localhost:1234")
2483 .request_config(RequestConfig::new().disable_retry())
2484 .sqlite_store(sqlite_path, None)
2485 .build()
2486 .await
2487 .unwrap();
2488 client2
2489 .matrix_auth()
2490 .restore_session(session, RoomLoadSettings::default())
2491 .await
2492 .unwrap();
2493
2494 client2
2495 .encryption()
2496 .enable_cross_process_store_lock("client2".to_owned())
2497 .await
2498 .unwrap();
2499
2500 let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2501 assert!(guard.is_some());
2502
2503 drop(guard);
2504 tokio::time::sleep(Duration::from_millis(100)).await;
2505 }
2506
2507 {
2508 let acquired = client.encryption().try_lock_store_once().await.unwrap();
2509 assert!(acquired.is_some());
2510 }
2511
2512 let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2514 assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2515
2516 {
2517 let acquired = client.encryption().try_lock_store_once().await.unwrap();
2518 assert!(acquired.is_some());
2519 }
2520
2521 let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2523 assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2524 }
2525
2526 #[async_test]
2527 async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2528 let client = no_retry_test_client(None).await;
2530 let server = MockServer::start().await;
2531
2532 let mut verification_state = client.encryption().verification_state();
2534
2535 assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2537
2538 let keys_requested = Arc::new(AtomicBool::new(false));
2541 let inner_bool = keys_requested.clone();
2542
2543 Mock::given(method("GET"))
2544 .and(path_regex(
2545 r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2546 ))
2547 .respond_with(move |_req: &Request| {
2548 inner_bool.fetch_or(true, Ordering::SeqCst);
2549 ResponseTemplate::new(200).set_body_json(json!({}))
2550 })
2551 .mount(&server)
2552 .await;
2553
2554 set_client_session(&client).await;
2556
2557 assert!(keys_requested.load(Ordering::SeqCst).not());
2559 assert_next_matches_with_timeout!(verification_state, VerificationState::Unverified);
2560 }
2561
2562 #[test]
2563 fn test_oauth_reset_info_from_uiaa_info() {
2564 let auth_info = json!({
2565 "session": "dummy",
2566 "flows": [
2567 {
2568 "stages": [
2569 "org.matrix.cross_signing_reset"
2570 ]
2571 }
2572 ],
2573 "params": {
2574 "org.matrix.cross_signing_reset": {
2575 "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2576 }
2577 },
2578 "msg": "To reset..."
2579 });
2580
2581 let auth_info = serde_json::from_value(auth_info)
2582 .expect("We should be able to deserialize the UiaaInfo");
2583 OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2584 .expect("We should be able to fetch the cross-signing reset info from the auth info");
2585 }
2586
2587 #[test]
2588 fn test_duplicate_one_time_key_error_parsing() {
2589 let message = concat!(
2590 r#"One time key signed_curve25519:AAAAAAAAAAA already exists. "#,
2591 r#"Old key: {"key":"dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs","#,
2592 r#""signatures":{"@example:matrix.org":{"ed25519:AAAAAAAAAA":""#,
2593 r#"Fk45zHAbrd+1j9wZXLjL2Y/+DU/Mnz9yuvlfYBOOT7qExN2Jdud+5BAuNs8nZ/caS4wTF39Kg3zQpzaGERoCBg"}}};"#,
2594 r#" new key: {'key': 'CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno', "#,
2595 r#"'signatures': {'@example:matrix.org': {'ed25519:AAAAAAAAAA': "#,
2596 r#"'BQ9Gp0p+6srF+c8OyruqKKd9R4yaub3THYAyyBB/7X/rG8BwcAqFynzl1aGyFYun4Q+087a5OSiglCXI+/kQAA'}}}"#
2597 );
2598 let message = DuplicateOneTimeKeyErrorMessage::from_str(message)
2599 .expect("We should be able to parse the error message");
2600
2601 assert_eq!(message.old_key.to_base64(), "dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs");
2602 assert_eq!(message.new_key.to_base64(), "CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno");
2603
2604 DuplicateOneTimeKeyErrorMessage::from_str("One time key already exists.")
2605 .expect_err("We shouldn't be able to parse an incomplete error message");
2606 }
2607
2608 fn devices_to_verify_against_keys_query_response(
2612 devices: Vec<serde_json::Value>,
2613 ) -> serde_json::Value {
2614 let device_keys: serde_json::Map<String, serde_json::Value> = devices
2615 .into_iter()
2616 .map(|device| (device.get("device_id").unwrap().as_str().unwrap().to_owned(), device))
2617 .collect();
2618 json!({
2619 "device_keys": {
2620 "@example:localhost": device_keys,
2621 },
2622 "master_keys": {
2623 "@example:localhost": {
2624 "keys": {
2625 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU",
2626 },
2627 "usage": ["master"],
2628 "user_id": "@example:localhost",
2629 },
2630 },
2631 "self_signing_keys": {
2632 "@example:localhost": {
2633 "keys": {
2634 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM",
2635 },
2636 "usage": ["self_signing"],
2637 "user_id": "@example:localhost",
2638 "signatures": {
2639 "@example:localhost": {
2640 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "etO1bB+rCk+TQ/FcjQ8eWu/RsRNQNNQ1Ek+PD6//j8yz6igRjfvuHZaMvr/quAFrirfgExph2TdOwlDgN5bFCQ",
2641 },
2642 },
2643 },
2644 },
2645 "user_signing_keys": {
2646 "@example:localhost": {
2647 "keys": {
2648 "ed25519:CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0": "CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0",
2649 },
2650 "usage": ["user_signing"],
2651 "user_id": "@example:localhost",
2652 "signatures": {
2653 "@example:localhost": {
2654 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "E/DFi/hQTIb/7eSB+HbCXeTLFaLjqWHzLO9GwjL1qdhfO7ew4p6YdtXSH3T2YYr1dKCPteH/4nMYVwOhww2CBg",
2655 },
2656 },
2657 },
2658 }
2659 })
2660 }
2661
2662 #[async_test]
2665 async fn test_devices_to_verify_against_no_devices() {
2668 let server = MockServer::start().await;
2669 let client = logged_in_client(Some(server.uri())).await;
2670
2671 Mock::given(method("POST"))
2672 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2673 .respond_with(
2674 ResponseTemplate::new(200)
2675 .set_body_json(devices_to_verify_against_keys_query_response(vec![])),
2676 )
2677 .mount(&server)
2678 .await;
2679
2680 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2681 }
2682
2683 #[async_test]
2684 async fn test_devices_to_verify_against_cross_signed() {
2687 let server = MockServer::start().await;
2688 let client = logged_in_client(Some(server.uri())).await;
2689
2690 Mock::given(method("POST"))
2691 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2692 .respond_with(ResponseTemplate::new(200).set_body_json(
2693 devices_to_verify_against_keys_query_response(vec![
2694 json!({
2695 "algorithms": [
2696 "m.olm.v1.curve25519-aes-sha2",
2697 "m.megolm.v1.aes-sha2",
2698 ],
2699 "user_id": "@example:localhost",
2700 "device_id": "SIGNEDDEVICE",
2701 "keys": {
2702 "curve25519:SIGNEDDEVICE": "o1LqUtH/sqd3WF+BB2Qr77uw3sDmZhMOz68/IV9aHxs",
2703 "ed25519:SIGNEDDEVICE": "iVoEfMOoUqxXVMLdpZCOgvQuCrT3/kQWkBmB3Phi/lo",
2704 },
2705 "signatures": {
2706 "@example:localhost": {
2707 "ed25519:SIGNEDDEVICE": "C7yRu1fNrdD2EobVdtANMqk3LBtWtTRWrIU22xVS8/Om1kmA/luzek64R3N6JsZhYczVmZYBKhUC9kRvHHwOBg",
2708 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "frfh2HP28GclmGvwTic00Fj4nZCvm4RlRA6U56mnD5920hOi04+L055ojzp6ybZXvC/GQYfyTHwQXlUN1nvxBA",
2709 },
2710 },
2711 })
2712 ])
2713 ))
2714 .mount(&server)
2715 .await;
2716
2717 assert!(client.encryption().has_devices_to_verify_against().await.unwrap());
2718 }
2719
2720 #[async_test]
2721 async fn test_devices_to_verify_against_dehydrated_and_unsigned() {
2724 let server = MockServer::start().await;
2725 let client = logged_in_client(Some(server.uri())).await;
2726 let user_id = client.user_id().unwrap();
2727 let olm_machine = client.olm_machine().await;
2728 let olm_machine = olm_machine.as_ref().unwrap();
2729
2730 Mock::given(method("POST"))
2731 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2732 .respond_with(ResponseTemplate::new(200).set_body_json(
2733 devices_to_verify_against_keys_query_response(vec![
2734 json!({
2735 "algorithms": [
2736 "m.olm.v1.curve25519-aes-sha2",
2737 "m.megolm.v1.aes-sha2",
2738 ],
2739 "user_id": "@example:localhost",
2740 "device_id": "DEHYDRATEDDEVICE",
2741 "keys": {
2742 "curve25519:DEHYDRATEDDEVICE": "XOn5VguAgokZ3p9mBz2yOB395fn6j75G8jIPcXEWQGY",
2743 "ed25519:DEHYDRATEDDEVICE": "4GG5xmBT7z4rgUgmWNlKZ+ABE3QlGgTorF+luCnKfYI",
2744 },
2745 "dehydrated": true,
2746 "signatures": {
2747 "@example:localhost": {
2748 "ed25519:DEHYDRATEDDEVICE": "+OMasB7nzVlMV+zRDxkh4h8h/Q0bY42P1SPv7X2IURIelT5G+d+AYSmg30N4maphxEDBqt/vI8/lIr71exc3Dg",
2749 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "8DzynAgbYgXX1Md5d4Vw91Zstpoi4dpG7levFeVhi4psCAWuBnV76Qu1s2TGjQQ0CLDXEqcxxuX9X4eUK5TGCg",
2750 },
2751 },
2752 }),
2753 json!({
2754 "algorithms": [
2755 "m.olm.v1.curve25519-aes-sha2",
2756 "m.megolm.v1.aes-sha2",
2757 ],
2758 "user_id": "@example:localhost",
2759 "device_id": "UNSIGNEDDEVICE",
2760 "keys": {
2761 "curve25519:UNSIGNEDDEVICE": "mMby6NpprkHxj+ONfO9Z5lBqVUHJBMkrPFSNJhogBkg",
2762 "ed25519:UNSIGNEDDEVICE": "Zifq39ZDrlIaSRf0Hh22owEqXCPE+1JSSgs6LDlubwQ",
2763 },
2764 "signatures": {
2765 "@example:localhost": {
2766 "ed25519:UNSIGNEDDEVICE": "+L29RoDKoTufPGm/Bae65KHno7Z1H7GYhxSKpB4RQZRS7NrR29AMW1PVhEsIozYuDVEFuMZ0L8H3dlcaHxagBA",
2767 },
2768 },
2769 }),
2770 ])
2771 ))
2772 .mount(&server)
2773 .await;
2774
2775 let (request_id, request) = olm_machine.query_keys_for_users([user_id]);
2776 client.keys_query(&request_id, request.device_keys).await.unwrap();
2777
2778 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2779 }
2780}