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::{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 futures;
110pub mod identities;
111pub mod recovery;
112pub mod secret_storage;
113pub(crate) mod tasks;
114pub mod verification;
115
116use matrix_sdk_base::crypto::OlmMachineBuilder;
117pub use matrix_sdk_base::crypto::{
118 CrossSigningStatus, CryptoStoreError, DecryptorError, EventError, KeyExportError, LocalTrust,
119 MediaEncryptionInfo, MegolmError, OlmError, RoomKeyImportResult, SessionCreationError,
120 SignatureError, VERSION,
121 olm::{
122 SessionCreationError as MegolmSessionCreationError,
123 SessionExportError as OlmSessionExportError,
124 },
125 vodozemac,
126};
127use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
128
129#[cfg(feature = "experimental-send-custom-to-device")]
130use crate::config::RequestConfig;
131pub use crate::error::RoomKeyImportError;
132
133#[cfg(feature = "sqlite")]
136#[derive(Debug, thiserror::Error)]
137pub enum BundleExportError {
138 #[error(transparent)]
140 OpenStoreError(#[from] matrix_sdk_sqlite::OpenStoreError),
141 #[error(transparent)]
143 StoreError(#[from] CryptoStoreError),
144 #[error(transparent)]
147 SecretExport(#[from] matrix_sdk_base::crypto::store::SecretsBundleExportError),
148}
149
150#[derive(Debug, thiserror::Error)]
153pub enum BundleImportError {
154 #[error(transparent)]
156 SecretImport(#[from] SecretImportError),
157 #[error(transparent)]
159 DeviceKeys(#[from] Error),
160}
161
162#[cfg(feature = "sqlite")]
171pub async fn export_secrets_bundle_from_store(
172 database_path: impl AsRef<Path>,
173 passphrase: Option<&str>,
174) -> std::result::Result<Option<(OwnedUserId, SecretsBundle)>, BundleExportError> {
175 use matrix_sdk_base::crypto::store::CryptoStore;
176
177 let store = matrix_sdk_sqlite::SqliteCryptoStore::open(database_path, passphrase).await?;
178 let account =
179 store.load_account().await.map_err(|e| BundleExportError::StoreError(e.into()))?;
180
181 if let Some(account) = account {
182 let machine = OlmMachineBuilder::new(&account.user_id, &account.device_id)
183 .with_crypto_store(store)
184 .build()
185 .await
186 .map_err(BundleExportError::StoreError)?;
187
188 let bundle = machine.store().export_secrets_bundle().await?;
189
190 Ok(Some((account.user_id.to_owned(), bundle)))
191 } else {
192 Ok(None)
193 }
194}
195
196pub(crate) struct EncryptionData {
198 pub tasks: StdMutex<ClientTasks>,
201
202 pub encryption_settings: EncryptionSettings,
204
205 pub backup_state: BackupClientState,
207
208 pub recovery_state: SharedObservable<RecoveryState>,
210}
211
212impl EncryptionData {
213 pub fn new(encryption_settings: EncryptionSettings) -> Self {
214 Self {
215 encryption_settings,
216
217 tasks: StdMutex::new(Default::default()),
218 backup_state: Default::default(),
219 recovery_state: Default::default(),
220 }
221 }
222
223 pub fn initialize_tasks(&self, client: &Arc<ClientInner>) {
224 let weak_client = WeakClient::from_inner(client);
225
226 let mut tasks = self.tasks.lock();
227 tasks.upload_room_keys = Some(BackupUploadingTask::new(weak_client.clone()));
228
229 if self.encryption_settings.backup_download_strategy
230 == BackupDownloadStrategy::AfterDecryptionFailure
231 {
232 tasks.download_room_keys = Some(BackupDownloadTask::new(weak_client));
233 }
234 }
235
236 pub fn initialize_recovery_state_update_task(&self, client: &Client) {
242 let mut guard = self.tasks.lock();
243
244 let future = Recovery::update_state_after_backup_state_change(client);
245 let join_handle = spawn(future);
246
247 guard.update_recovery_state_after_backup = Some(join_handle);
248 }
249}
250
251#[derive(Clone, Copy, Debug, Default)]
253pub struct EncryptionSettings {
254 pub auto_enable_cross_signing: bool,
260
261 pub backup_download_strategy: BackupDownloadStrategy,
266
267 pub auto_enable_backups: bool,
269}
270
271#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
273#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
274pub enum BackupDownloadStrategy {
275 OneShot,
286
287 AfterDecryptionFailure,
289
290 #[default]
295 Manual,
296}
297
298#[derive(Clone, Copy, Debug, Eq, PartialEq)]
303pub enum VerificationState {
304 Unknown,
306 Verified,
309 Unverified,
311}
312
313#[derive(Debug)]
324pub struct CrossSigningResetHandle {
325 client: Client,
326 upload_request: UploadSigningKeysRequest,
327 signatures_request: UploadSignaturesRequest,
328 auth_type: CrossSigningResetAuthType,
329 is_cancelled: Mutex<bool>,
330}
331
332impl CrossSigningResetHandle {
333 pub fn new(
335 client: Client,
336 upload_request: UploadSigningKeysRequest,
337 signatures_request: UploadSignaturesRequest,
338 auth_type: CrossSigningResetAuthType,
339 ) -> Self {
340 Self {
341 client,
342 upload_request,
343 signatures_request,
344 auth_type,
345 is_cancelled: Mutex::new(false),
346 }
347 }
348
349 pub fn auth_type(&self) -> &CrossSigningResetAuthType {
352 &self.auth_type
353 }
354
355 pub async fn auth(&self, auth: Option<AuthData>) -> Result<()> {
359 const RETRY_EVERY: Duration = Duration::from_millis(500);
361
362 const TIMEOUT: Duration = Duration::from_mins(2);
364
365 timeout(
366 async {
367 let mut upload_request = self.upload_request.clone();
368 upload_request.auth = auth;
369
370 debug!(
371 "Repeatedly PUTting to keys/device_signing/upload until it works \
372 or we hit a permanent failure."
373 );
374 while let Err(e) = self.client.send(upload_request.clone()).await {
375 if *self.is_cancelled.lock().await {
376 return Ok(());
377 }
378
379 match e.as_uiaa_response() {
380 Some(uiaa_info) => {
381 if !matches!(self.auth_type, CrossSigningResetAuthType::OAuth(_))
384 && uiaa_info.auth_error.is_some()
385 {
386 return Err(e.into());
387 }
388 }
389 None => return Err(e.into()),
390 }
391
392 debug!(
393 "PUT to keys/device_signing/upload failed with 401. Retrying after \
394 a short delay."
395 );
396 sleep(RETRY_EVERY).await;
397 }
398
399 self.client.send(self.signatures_request.clone()).await?;
400
401 Ok(())
402 },
403 TIMEOUT,
404 )
405 .await
406 .unwrap_or_else(|_| {
407 warn!("Timed out waiting for keys/device_signing/upload to succeed.");
408 Err(Error::Timeout)
409 })
410 }
411
412 pub async fn cancel(&self) {
414 *self.is_cancelled.lock().await = true;
415 }
416}
417
418#[derive(Debug, Clone)]
421pub enum CrossSigningResetAuthType {
422 Uiaa(UiaaInfo),
424 OAuth(OAuthCrossSigningResetInfo),
427}
428
429impl CrossSigningResetAuthType {
430 fn new(error: &HttpError) -> Result<Option<Self>> {
431 if let Some(auth_info) = error.as_uiaa_response() {
432 if let Ok(Some(auth_info)) = OAuthCrossSigningResetInfo::from_auth_info(auth_info) {
433 Ok(Some(CrossSigningResetAuthType::OAuth(auth_info)))
434 } else {
435 Ok(Some(CrossSigningResetAuthType::Uiaa(auth_info.clone())))
436 }
437 } else {
438 Ok(None)
439 }
440 }
441}
442
443#[derive(Debug, Clone, Deserialize)]
446pub struct OAuthCrossSigningResetInfo {
447 pub approval_url: Url,
449
450 pub session: Option<String>,
452}
453
454impl OAuthCrossSigningResetInfo {
455 fn from_auth_info(auth_info: &UiaaInfo) -> Result<Option<Self>> {
456 let Some(parameters) = auth_info.params::<OAuthParams>(&AuthType::OAuth)? else {
457 return Ok(None);
458 };
459
460 Ok(Some(OAuthCrossSigningResetInfo {
461 approval_url: parameters.url.as_str().try_into()?,
462 session: auth_info.session.clone(),
463 }))
464 }
465}
466
467#[derive(Clone, Debug)]
470pub struct DuplicateOneTimeKeyErrorMessage {
471 pub old_key: Curve25519PublicKey,
473 pub new_key: Curve25519PublicKey,
475}
476
477impl FromStr for DuplicateOneTimeKeyErrorMessage {
478 type Err = serde_json::Error;
479
480 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
481 let mut split = s.split_terminator(';');
485
486 let old_key = split
487 .next()
488 .ok_or(serde_json::Error::custom("Old key is missing in the error message"))?;
489 let new_key = split
490 .next()
491 .ok_or(serde_json::Error::custom("New key is missing in the error message"))?;
492
493 let old_key_index = old_key
496 .find("Old key:")
497 .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
498
499 let old_key = old_key[old_key_index..]
500 .trim()
501 .strip_prefix("Old key:")
502 .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
503
504 let new_key = new_key
507 .trim()
508 .strip_prefix("new key:")
509 .ok_or(serde_json::Error::custom("New key is missing the prefix"))?;
510
511 let new_key = new_key.replace("'", "\"");
514
515 let old_key: SignedKey = serde_json::from_str(old_key)?;
517 let new_key: SignedKey = serde_json::from_str(&new_key)?;
518
519 let old_key = old_key.key();
521 let new_key = new_key.key();
522
523 Ok(Self { old_key, new_key })
524 }
525}
526
527impl Client {
528 pub(crate) async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
529 self.base_client().olm_machine().await
530 }
531
532 pub(crate) async fn mark_request_as_sent(
533 &self,
534 request_id: &TransactionId,
535 response: impl Into<matrix_sdk_base::crypto::types::requests::AnyIncomingResponse<'_>>,
536 ) -> Result<(), matrix_sdk_base::Error> {
537 Ok(self
538 .olm_machine()
539 .await
540 .as_ref()
541 .expect(
542 "We should have an olm machine once we try to mark E2EE related requests as sent",
543 )
544 .mark_request_as_sent(request_id, response)
545 .await?)
546 }
547
548 #[instrument(skip(self, device_keys))]
554 pub(crate) async fn keys_query(
555 &self,
556 request_id: &TransactionId,
557 device_keys: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
558 ) -> Result<get_keys::v3::Response> {
559 let request = assign!(get_keys::v3::Request::new(), { device_keys });
560
561 let response = self.send(request).await?;
562 self.mark_request_as_sent(request_id, &response).await?;
563 self.encryption().update_state_after_keys_query(&response).await;
564
565 Ok(response)
566 }
567
568 pub fn upload_encrypted_file<'a, R: Read + ?Sized + 'a>(
602 &'a self,
603 reader: &'a mut R,
604 ) -> UploadEncryptedFile<'a, R> {
605 UploadEncryptedFile::new(self, reader)
606 }
607
608 pub(crate) async fn upload_encrypted_media_and_thumbnail(
611 &self,
612 data: &[u8],
613 thumbnail: Option<Thumbnail>,
614 send_progress: SharedObservable<TransmissionProgress>,
615 ) -> Result<(MediaSource, Option<(MediaSource, Box<ThumbnailInfo>)>)> {
616 let upload_thumbnail = self.upload_encrypted_thumbnail(thumbnail, send_progress.clone());
617
618 let upload_attachment = async {
619 let mut cursor = Cursor::new(data);
620 self.upload_encrypted_file(&mut cursor)
621 .with_send_progress_observable(send_progress)
622 .await
623 };
624
625 let (thumbnail, file) = try_join(upload_thumbnail, upload_attachment).await?;
626
627 Ok((MediaSource::Encrypted(Box::new(file)), thumbnail))
628 }
629
630 async fn upload_encrypted_thumbnail(
633 &self,
634 thumbnail: Option<Thumbnail>,
635 send_progress: SharedObservable<TransmissionProgress>,
636 ) -> Result<Option<(MediaSource, Box<ThumbnailInfo>)>> {
637 let Some(thumbnail) = thumbnail else {
638 return Ok(None);
639 };
640
641 let (data, _, thumbnail_info) = thumbnail.into_parts();
642 let mut cursor = Cursor::new(data);
643
644 let file = self
645 .upload_encrypted_file(&mut cursor)
646 .with_send_progress_observable(send_progress)
647 .await?;
648
649 Ok(Some((MediaSource::Encrypted(Box::new(file)), thumbnail_info)))
650 }
651
652 pub(crate) async fn claim_one_time_keys(
658 &self,
659 users: impl Iterator<Item = &UserId>,
660 ) -> Result<()> {
661 let _lock = self.locks().key_claim_lock.lock().await;
662
663 if let Some((request_id, request)) = self
664 .olm_machine()
665 .await
666 .as_ref()
667 .ok_or(Error::NoOlmMachine)?
668 .get_missing_sessions(users)
669 .await?
670 {
671 let response = self.send(request).await?;
672 self.mark_request_as_sent(&request_id, &response).await?;
673 }
674
675 Ok(())
676 }
677
678 #[instrument(skip(self, request))]
688 pub(crate) async fn keys_upload(
689 &self,
690 request_id: &TransactionId,
691 request: &upload_keys::v3::Request,
692 ) -> Result<upload_keys::v3::Response> {
693 debug!(
694 device_keys = request.device_keys.is_some(),
695 one_time_key_count = request.one_time_keys.len(),
696 "Uploading public encryption keys",
697 );
698
699 let response = self.send(request.clone()).await?;
700 self.mark_request_as_sent(request_id, &response).await?;
701
702 Ok(response)
703 }
704
705 pub(crate) async fn room_send_helper(
706 &self,
707 request: &RoomMessageRequest,
708 ) -> Result<send_message_event::v3::Response> {
709 let content = request.content.clone();
710 let txn_id = request.txn_id.clone();
711 let room_id = &request.room_id;
712
713 self.get_room(room_id)
714 .expect("Can't send a message to a room that isn't known to the store")
715 .send(*content)
716 .with_transaction_id(txn_id)
717 .await
718 .map(|result| result.response)
719 }
720
721 pub(crate) async fn send_to_device(
722 &self,
723 request: &ToDeviceRequest,
724 ) -> HttpResult<ToDeviceResponse> {
725 let request = RumaToDeviceRequest::new_raw(
726 request.event_type.clone(),
727 request.txn_id.clone(),
728 request.messages.clone(),
729 );
730
731 self.send(request).await
732 }
733
734 pub(crate) async fn send_verification_request(
735 &self,
736 request: OutgoingVerificationRequest,
737 ) -> Result<()> {
738 use matrix_sdk_base::crypto::types::requests::OutgoingVerificationRequest::*;
739
740 match request {
741 ToDevice(t) => {
742 self.send_to_device(&t).await?;
743 }
744 InRoom(r) => {
745 self.room_send_helper(&r).await?;
746 }
747 }
748
749 Ok(())
750 }
751
752 async fn send_outgoing_request(&self, r: OutgoingRequest) -> Result<()> {
753 use matrix_sdk_base::crypto::types::requests::AnyOutgoingRequest;
754
755 match r.request() {
756 AnyOutgoingRequest::KeysQuery(request) => {
757 self.keys_query(r.request_id(), request.device_keys.clone()).await?;
758 }
759 AnyOutgoingRequest::KeysUpload(request) => {
760 let response = self.keys_upload(r.request_id(), request).await;
761
762 if let Err(e) = &response {
763 match e.as_client_api_error() {
764 Some(e) if e.status_code == 400 => {
765 if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &e.body
766 {
767 {
772 let already_reported = self
773 .state_store()
774 .get_kv_data(StateStoreDataKey::OneTimeKeyAlreadyUploaded)
775 .await?
776 .is_some();
777
778 if message.starts_with("One time key") && !already_reported {
779 let error_message =
780 DuplicateOneTimeKeyErrorMessage::from_str(message);
781
782 if let Ok(message) = &error_message {
783 error!(
784 sentry = true,
785 old_key = %message.old_key,
786 new_key = %message.new_key,
787 "Duplicate one-time keys have been uploaded"
788 );
789 } else {
790 error!(
791 sentry = true,
792 "Duplicate one-time keys have been uploaded"
793 );
794 }
795
796 self.state_store()
797 .set_kv_data(
798 StateStoreDataKey::OneTimeKeyAlreadyUploaded,
799 StateStoreDataValue::OneTimeKeyAlreadyUploaded,
800 )
801 .await?;
802
803 if let Err(e) = self
804 .inner
805 .duplicate_key_upload_error_sender
806 .send(error_message.ok())
807 {
808 error!(
809 "Failed to dispatch duplicate key upload error notification: {}",
810 e
811 );
812 }
813 }
814 }
815 }
816 }
817 _ => {}
818 }
819
820 response?;
821 }
822 }
823 AnyOutgoingRequest::ToDeviceRequest(request) => {
824 let response = self.send_to_device(request).await?;
825 self.mark_request_as_sent(r.request_id(), &response).await?;
826 }
827 AnyOutgoingRequest::SignatureUpload(request) => {
828 let response = self.send(request.clone()).await?;
829 self.mark_request_as_sent(r.request_id(), &response).await?;
830 }
831 AnyOutgoingRequest::RoomMessage(request) => {
832 let response = self.room_send_helper(request).await?;
833 self.mark_request_as_sent(r.request_id(), &response).await?;
834 }
835 AnyOutgoingRequest::KeysClaim(request) => {
836 let response = self.send(request.clone()).await?;
837 self.mark_request_as_sent(r.request_id(), &response).await?;
838 }
839 }
840
841 Ok(())
842 }
843
844 #[instrument(skip_all)]
845 pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
846 const MAX_CONCURRENT_REQUESTS: usize = 20;
847
848 if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
851 warn!("Error while claiming one-time keys {:?}", e);
852 }
853
854 let outgoing_requests = stream::iter(
855 self.olm_machine()
856 .await
857 .as_ref()
858 .ok_or(Error::NoOlmMachine)?
859 .outgoing_requests()
860 .await?,
861 )
862 .map(|r| self.send_outgoing_request(r));
863
864 let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
865
866 requests
867 .for_each(|r| async move {
868 match r {
869 Ok(_) => (),
870 Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
871 }
872 })
873 .await;
874
875 Ok(())
876 }
877}
878
879#[cfg(any(feature = "testing", test))]
880impl Client {
881 pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
883 self.olm_machine().await
884 }
885
886 pub fn abort_bundle_receiver_task(&self) {
888 let tasks = self.inner.e2ee.tasks.lock();
889 if let Some(task) = tasks.receive_historic_room_key_bundles.as_ref() {
890 task.abort()
891 }
892 }
893}
894
895#[derive(Debug, Clone)]
899pub struct Encryption {
900 client: Client,
902}
903
904impl Encryption {
905 pub(crate) fn new(client: Client) -> Self {
906 Self { client }
907 }
908
909 pub(crate) fn settings(&self) -> EncryptionSettings {
911 self.client.inner.e2ee.encryption_settings
912 }
913
914 pub async fn ed25519_key(&self) -> Option<String> {
917 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
918 }
919
920 pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
922 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
923 }
924
925 pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
927 match self.get_own_device().await {
928 Ok(Some(device)) => device.first_time_seen_ts(),
929 _ => MilliSecondsSinceUnixEpoch::now(),
931 }
932 }
933
934 pub async fn import_secrets_bundle(
947 &self,
948 bundle: &SecretsBundle,
949 ) -> Result<(), BundleImportError> {
950 self.import_secrets_bundle_impl(bundle).await?;
951
952 self.ensure_device_keys_upload().await?;
955 self.wait_for_e2ee_initialization_tasks().await;
956
957 if !self.backups().are_enabled().await {
962 self.backups().maybe_resume_backups().await?;
963 }
964
965 Ok(())
966 }
967
968 pub(crate) async fn import_secrets_bundle_impl(
969 &self,
970 bundle: &SecretsBundle,
971 ) -> Result<(), SecretImportError> {
972 let olm_machine = self.client.olm_machine().await;
973 let olm_machine =
974 olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
975
976 olm_machine.store().import_secrets_bundle(bundle).await
977 }
978
979 pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
984 let olm = self.client.olm_machine().await;
985 let machine = olm.as_ref()?;
986 Some(machine.cross_signing_status().await)
987 }
988
989 pub async fn has_devices_to_verify_against(&self) -> Result<bool> {
995 let olm_machine = self.client.olm_machine().await;
996 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
997 let user_id = olm_machine.user_id();
998
999 self.ensure_initial_key_query().await?;
1000
1001 let devices = self.get_user_devices(user_id).await?;
1002
1003 let ret = devices.devices().any(|device| {
1004 device.is_cross_signed_by_owner()
1005 && device.curve25519_key().is_some()
1006 && !device.is_dehydrated()
1007 });
1008
1009 Ok(ret)
1010 }
1011
1012 pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
1017 if let Some(machine) = self.client.olm_machine().await.as_ref() {
1018 machine.tracked_users().await
1019 } else {
1020 Ok(HashSet::new())
1021 }
1022 }
1023
1024 pub fn verification_state(&self) -> Subscriber<VerificationState> {
1047 self.client.inner.verification_state.subscribe_reset()
1048 }
1049
1050 pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
1052 let olm = self.client.olm_machine().await;
1053 let olm = olm.as_ref()?;
1054 #[allow(clippy::bind_instead_of_map)]
1055 olm.get_verification(user_id, flow_id).and_then(|v| match v {
1056 matrix_sdk_base::crypto::Verification::SasV1(sas) => {
1057 Some(SasVerification { inner: sas, client: self.client.clone() }.into())
1058 }
1059 #[cfg(feature = "qrcode")]
1060 matrix_sdk_base::crypto::Verification::QrV1(qr) => {
1061 Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
1062 }
1063 _ => None,
1064 })
1065 }
1066
1067 pub async fn get_verification_request(
1070 &self,
1071 user_id: &UserId,
1072 flow_id: impl AsRef<str>,
1073 ) -> Option<VerificationRequest> {
1074 let olm = self.client.olm_machine().await;
1075 let olm = olm.as_ref()?;
1076
1077 olm.get_verification_request(user_id, flow_id)
1078 .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
1079 }
1080
1081 pub async fn get_device(
1115 &self,
1116 user_id: &UserId,
1117 device_id: &DeviceId,
1118 ) -> Result<Option<Device>, CryptoStoreError> {
1119 let olm = self.client.olm_machine().await;
1120 let Some(machine) = olm.as_ref() else { return Ok(None) };
1121 let device = machine.get_device(user_id, device_id, None).await?;
1122 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1123 }
1124
1125 pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
1132 let olm = self.client.olm_machine().await;
1133 let Some(machine) = olm.as_ref() else { return Ok(None) };
1134 let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
1135 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1136 }
1137
1138 pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
1164 let devices = self
1165 .client
1166 .olm_machine()
1167 .await
1168 .as_ref()
1169 .ok_or(Error::NoOlmMachine)?
1170 .get_user_devices(user_id, None)
1171 .await?;
1172
1173 Ok(UserDevices { inner: devices, client: self.client.clone() })
1174 }
1175
1176 pub async fn get_user_identity(
1212 &self,
1213 user_id: &UserId,
1214 ) -> Result<Option<UserIdentity>, CryptoStoreError> {
1215 let olm = self.client.olm_machine().await;
1216 let Some(olm) = olm.as_ref() else { return Ok(None) };
1217 let identity = olm.get_identity(user_id, None).await?;
1218
1219 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1220 }
1221
1222 pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
1260 let olm = self.client.olm_machine().await;
1261 let Some(olm) = olm.as_ref() else { return Ok(None) };
1262
1263 let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
1264 self.client.keys_query(&request_id, request.device_keys).await?;
1265
1266 let identity = olm.get_identity(user_id, None).await?;
1267 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1268 }
1269
1270 pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates> + use<>> {
1301 let olm = self.client.olm_machine().await;
1302 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1303 let client = self.client.to_owned();
1304
1305 Ok(olm
1306 .store()
1307 .devices_stream()
1308 .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1309 }
1310
1311 pub async fn user_identities_stream(
1339 &self,
1340 ) -> Result<impl Stream<Item = IdentityUpdates> + use<>> {
1341 let olm = self.client.olm_machine().await;
1342 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1343 let client = self.client.to_owned();
1344
1345 Ok(olm
1346 .store()
1347 .user_identities_stream()
1348 .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1349 }
1350
1351 pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1390 let olm = self.client.olm_machine().await;
1391 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1392
1393 let CrossSigningBootstrapRequests {
1394 upload_signing_keys_req,
1395 upload_keys_req,
1396 upload_signatures_req,
1397 } = olm.bootstrap_cross_signing(false).await?;
1398
1399 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1400 auth: auth_data,
1401 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1402 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1403 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1404 });
1405
1406 if let Some(req) = upload_keys_req {
1407 self.client.send_outgoing_request(req).await?;
1408 }
1409 self.client.send(upload_signing_keys_req).await?;
1410 self.client.send(upload_signatures_req).await?;
1411
1412 Ok(())
1413 }
1414
1415 pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1456 let olm = self.client.olm_machine().await;
1457 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1458
1459 let CrossSigningBootstrapRequests {
1460 upload_keys_req,
1461 upload_signing_keys_req,
1462 upload_signatures_req,
1463 } = olm.bootstrap_cross_signing(true).await?;
1464
1465 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1466 auth: None,
1467 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1468 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1469 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1470 });
1471
1472 if let Some(req) = upload_keys_req {
1473 self.client.send_outgoing_request(req).await?;
1474 }
1475
1476 if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1477 if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1478 let client = self.client.clone();
1479
1480 Ok(Some(CrossSigningResetHandle::new(
1481 client,
1482 upload_signing_keys_req,
1483 upload_signatures_req,
1484 auth_type,
1485 )))
1486 } else {
1487 Err(error.into())
1488 }
1489 } else {
1490 self.client.send(upload_signatures_req).await?;
1491
1492 Ok(None)
1493 }
1494 }
1495
1496 async fn ensure_initial_key_query(&self) -> Result<()> {
1499 let olm_machine = self.client.olm_machine().await;
1500 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1501
1502 let user_id = olm_machine.user_id();
1503
1504 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1505 let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1506 self.client.keys_query(&request_id, request.device_keys).await?;
1507 }
1508
1509 Ok(())
1510 }
1511
1512 pub async fn bootstrap_cross_signing_if_needed(
1559 &self,
1560 auth_data: Option<AuthData>,
1561 ) -> Result<()> {
1562 let olm_machine = self.client.olm_machine().await;
1563 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1564 let user_id = olm_machine.user_id();
1565
1566 self.ensure_initial_key_query().await?;
1567
1568 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1569 self.bootstrap_cross_signing(auth_data).await?;
1570 }
1571
1572 Ok(())
1573 }
1574
1575 #[cfg(not(target_family = "wasm"))]
1627 pub async fn export_room_keys(
1628 &self,
1629 path: PathBuf,
1630 passphrase: &str,
1631 predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1632 ) -> Result<()> {
1633 let olm = self.client.olm_machine().await;
1634 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1635
1636 let keys = olm.store().export_room_keys(predicate).await?;
1637 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1638
1639 let encrypt = move || -> Result<()> {
1640 let export: String =
1641 matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1642 let mut file = std::fs::File::create(path)?;
1643 file.write_all(&export.into_bytes())?;
1644 Ok(())
1645 };
1646
1647 let task = tokio::task::spawn_blocking(encrypt);
1648 task.await.expect("Task join error")
1649 }
1650
1651 #[cfg(not(target_family = "wasm"))]
1689 pub async fn import_room_keys(
1690 &self,
1691 path: PathBuf,
1692 passphrase: &str,
1693 ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1694 let olm = self.client.olm_machine().await;
1695 let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1696 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1697
1698 let decrypt = move || {
1699 let file = std::fs::File::open(path)?;
1700 matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1701 };
1702
1703 let task = tokio::task::spawn_blocking(decrypt);
1704 let import = task.await.expect("Task join error")?;
1705
1706 let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1707
1708 self.backups().maybe_trigger_backup();
1709
1710 Ok(ret)
1711 }
1712
1713 pub async fn room_keys_received_stream(
1744 &self,
1745 ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<>>
1746 {
1747 let olm = self.client.olm_machine().await;
1748 let olm = olm.as_ref()?;
1749
1750 Some(olm.store().room_keys_received_stream())
1751 }
1752
1753 pub async fn historic_room_key_stream(
1784 &self,
1785 ) -> Option<impl Stream<Item = RoomKeyBundleInfo> + use<>> {
1786 let olm = self.client.olm_machine().await;
1787 let olm = olm.as_ref()?;
1788
1789 Some(olm.store().historic_room_key_stream())
1790 }
1791
1792 pub fn secret_storage(&self) -> SecretStorage {
1794 SecretStorage { client: self.client.to_owned() }
1795 }
1796
1797 pub fn backups(&self) -> Backups {
1799 Backups { client: self.client.to_owned() }
1800 }
1801
1802 pub fn recovery(&self) -> Recovery {
1804 Recovery { client: self.client.to_owned() }
1805 }
1806
1807 pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
1819 if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1821 let prev_holder = prev_lock.lock_holder();
1822 if prev_holder.is_some() && prev_holder.unwrap() == lock_value {
1823 return Ok(());
1824 }
1825 warn!(
1826 "Recreating cross-process store lock with a different holder value: \
1827 prev was {prev_holder:?}, new is {lock_value}"
1828 );
1829 }
1830
1831 let olm_machine = self.client.base_client().olm_machine().await;
1832 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1833
1834 let lock = olm_machine.store().create_store_lock(
1835 "cross_process_lock".to_owned(),
1836 CrossProcessLockConfig::multi_process(lock_value.to_owned()),
1837 );
1838
1839 {
1844 let lock_result = lock.try_lock_once().await?;
1845
1846 if lock_result.is_ok() {
1847 olm_machine
1848 .initialize_crypto_store_generation(
1849 &self.client.locks().crypto_store_generation,
1850 )
1851 .await?;
1852 }
1853 }
1854
1855 self.client
1856 .locks()
1857 .cross_process_crypto_store_lock
1858 .set(lock)
1859 .map_err(|_| Error::BadCryptoStoreState)?;
1860
1861 Ok(())
1862 }
1863
1864 #[instrument(skip(self), fields(olm_machine_new_generation, olm_machine_generation))]
1869 async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
1870 let olm_machine_guard = self.client.olm_machine().await;
1871 if let Some(olm_machine) = olm_machine_guard.as_ref() {
1872 let (new_gen, generation_number) = olm_machine
1873 .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
1874 .await?;
1875
1876 Span::current()
1877 .record("olm_machine_new_generation", new_gen)
1878 .record("olm_machine_generation", generation_number);
1879 debug!("OlmMachine generation maintained in CryptoStore");
1880
1881 if new_gen {
1883 drop(olm_machine_guard);
1885 self.client.base_client().regenerate_olm(None).await?;
1887 }
1888 Ok(generation_number)
1889 } else {
1890 warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
1895 Ok(0)
1896 }
1897 }
1898
1899 pub async fn spin_lock_store(
1907 &self,
1908 max_backoff: Option<u32>,
1909 ) -> Result<Option<CrossProcessLockGuard>, Error> {
1910 self.lock_store(async move |lock| lock.spin_lock(max_backoff).await).await
1911 }
1912
1913 pub async fn try_lock_store_once(&self) -> Result<Option<CrossProcessLockGuard>, Error> {
1921 match self.lock_store(CrossProcessLock::try_lock_once).await {
1922 Err(Error::CrossProcessLockError(e))
1923 if matches!(*e, CrossProcessLockError::Unobtained(_)) =>
1924 {
1925 Ok(None)
1926 }
1927 other => other,
1928 }
1929 }
1930
1931 pub async fn lock_store<F: AcquireCrossProcessLockFn<LockableCryptoStore>>(
1938 &self,
1939 acquire: F,
1940 ) -> Result<Option<CrossProcessLockGuard>, Error> {
1941 let wrap_err = |e: CryptoStoreError| {
1942 Error::CrossProcessLockError(Box::new(CrossProcessLockError::TryLock(Arc::new(e))))
1943 };
1944 if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1945 let guard = acquire(lock).await.map_err(wrap_err)??;
1946 let _ = self.on_lock_newly_acquired().await?;
1947 Ok(Some(guard.into_guard()))
1948 } else {
1949 Ok(None)
1950 }
1951 }
1952
1953 #[cfg(any(test, feature = "testing"))]
1955 pub async fn uploaded_key_count(&self) -> Result<u64> {
1956 let olm_machine = self.client.olm_machine().await;
1957 let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
1958 Ok(olm_machine.uploaded_key_count().await?)
1959 }
1960
1961 pub(crate) async fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
1985 let bundle_receiver_task = if self.client.inner.enable_share_history_on_invite {
1989 Some(BundleReceiverTask::new(&self.client).await)
1990 } else {
1991 None
1992 };
1993
1994 let mut tasks = self.client.inner.e2ee.tasks.lock();
1995
1996 let this = self.clone();
1997
1998 tasks.setup_e2ee = Some(spawn(async move {
1999 this.update_verification_state().await;
2002
2003 if this.settings().auto_enable_cross_signing
2004 && let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await
2005 {
2006 error!("Couldn't bootstrap cross signing {e:?}");
2007 }
2008
2009 if let Err(e) = this.backups().setup_and_resume().await {
2010 error!("Couldn't setup and resume backups {e:?}");
2011 }
2012 if let Err(e) = this.recovery().setup().await {
2013 error!("Couldn't setup and resume recovery {e:?}");
2014 }
2015 }));
2016
2017 tasks.receive_historic_room_key_bundles = bundle_receiver_task;
2018
2019 self.setup_room_membership_session_discard_handler();
2020 }
2021
2022 pub async fn wait_for_e2ee_initialization_tasks(&self) {
2025 let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
2026
2027 if let Some(task) = task
2028 && let Err(err) = task.await
2029 {
2030 warn!("Error when initializing backups: {err}");
2031 }
2032 }
2033
2034 pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
2044 let olm = self.client.olm_machine().await;
2045 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
2046
2047 if let Some((request_id, request)) = olm.upload_device_keys().await? {
2048 self.client.keys_upload(&request_id, &request).await?;
2049
2050 let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
2051 self.client.keys_query(&request_id, request.device_keys).await?;
2052 }
2053
2054 Ok(())
2055 }
2056
2057 pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
2058 self.recovery().update_state_after_keys_query(response).await;
2059
2060 if let Some(user_id) = self.client.user_id() {
2062 let contains_own_device = response.device_keys.contains_key(user_id);
2063
2064 if contains_own_device {
2065 self.update_verification_state().await;
2066 }
2067 }
2068 }
2069
2070 async fn update_verification_state(&self) {
2071 match self.get_own_device().await {
2072 Ok(device) => {
2073 if let Some(device) = device {
2074 let is_verified = device.is_cross_signed_by_owner();
2075
2076 if is_verified {
2077 self.client.inner.verification_state.set(VerificationState::Verified);
2078 } else {
2079 self.client.inner.verification_state.set(VerificationState::Unverified);
2080 }
2081 } else {
2082 warn!("Couldn't find out own device in the store.");
2083 self.client.inner.verification_state.set(VerificationState::Unknown);
2084 }
2085 }
2086 Err(error) => {
2087 warn!("Failed retrieving own device: {error}");
2088 self.client.inner.verification_state.set(VerificationState::Unknown);
2089 }
2090 }
2091 }
2092
2093 fn setup_room_membership_session_discard_handler(&self) {
2113 let client = WeakClient::from_client(&self.client);
2114 self.client.add_event_handler(|ev: OriginalSyncRoomMemberEvent, room: Room| async move {
2115 let Some(client) = client.get() else {
2116 return;
2118 };
2119 let Some(user_id) = client.user_id() else {
2120 return;
2122 };
2123 let olm = client.olm_machine().await;
2124 let Some(olm) = olm.as_ref() else {
2125 warn!("Cannot discard session - Olm machine is not available");
2126 return;
2127 };
2128
2129 if matches!(
2130 ev.membership_change(),
2131 MembershipChange::Joined |
2132 MembershipChange::Invited |
2133 MembershipChange::KnockAccepted |
2134 MembershipChange::InvitationAccepted |
2135 MembershipChange::ProfileChanged { .. }
2136 ) || ev.sender == user_id {
2137 return;
2139 }
2140
2141 debug!(room_id = ?room.room_id(), member_id = ?ev.sender, "Discarding session as a user left the room");
2142
2143 if let Err(e) = olm.discard_room_key(room.room_id()).await {
2146 warn!(
2147 room_id = ?room.room_id(),
2148 "Error discarding room key after member leave: {e:?}"
2149 );
2150 }
2151 });
2152 }
2153
2154 #[cfg(feature = "experimental-send-custom-to-device")]
2163 pub async fn encrypt_and_send_raw_to_device(
2164 &self,
2165 recipient_devices: Vec<&Device>,
2166 event_type: &str,
2167 content: Raw<AnyToDeviceEventContent>,
2168 share_strategy: CollectStrategy,
2169 ) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
2170 let users = recipient_devices.iter().map(|device| device.user_id());
2171
2172 self.client.claim_one_time_keys(users).await?;
2176
2177 let olm = self.client.olm_machine().await;
2178 let olm = olm.as_ref().expect("Olm machine wasn't started");
2179
2180 let (requests, withhelds) = olm
2181 .encrypt_content_for_devices(
2182 recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
2183 event_type,
2184 &content
2185 .deserialize_as::<serde_json::Value>()
2186 .expect("Deserialize as Value will always work"),
2187 share_strategy,
2188 )
2189 .await?;
2190
2191 let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
2192
2193 withhelds.iter().for_each(|(d, _)| {
2195 failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
2196 });
2197
2198 for request in requests {
2200 let ruma_request = RumaToDeviceRequest::new_raw(
2201 request.event_type.clone(),
2202 request.txn_id.clone(),
2203 request.messages.clone(),
2204 );
2205
2206 let send_result = self
2207 .client
2208 .send_inner(ruma_request, Some(RequestConfig::short_retry()), Default::default())
2209 .await;
2210
2211 if send_result.is_err() {
2213 for (user_id, device_map) in request.messages {
2215 for device_id in device_map.keys() {
2216 match device_id {
2217 DeviceIdOrAllDevices::DeviceId(device_id) => {
2218 failures.push((user_id.clone(), device_id.to_owned()));
2219 }
2220 DeviceIdOrAllDevices::AllDevices => {
2221 }
2223 }
2224 }
2225 }
2226 }
2227 }
2228
2229 Ok(failures)
2230 }
2231}
2232
2233#[cfg(all(test, not(target_family = "wasm")))]
2234mod tests {
2235 use std::{
2236 ops::Not,
2237 str::FromStr,
2238 sync::{
2239 Arc,
2240 atomic::{AtomicBool, Ordering},
2241 },
2242 time::Duration,
2243 };
2244
2245 use matrix_sdk_test::{
2246 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
2247 event_factory::EventFactory,
2248 };
2249 use ruma::{
2250 event_id,
2251 events::{reaction::ReactionEventContent, relation::Annotation},
2252 user_id,
2253 };
2254 use serde_json::json;
2255 use wiremock::{
2256 Mock, MockServer, Request, ResponseTemplate,
2257 matchers::{header, method, path_regex},
2258 };
2259
2260 use crate::{
2261 Client, assert_next_matches_with_timeout,
2262 config::RequestConfig,
2263 encryption::{
2264 DuplicateOneTimeKeyErrorMessage, OAuthCrossSigningResetInfo, VerificationState,
2265 },
2266 test_utils::{
2267 client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
2268 },
2269 };
2270
2271 #[async_test]
2272 async fn test_reaction_sending() {
2273 let server = MockServer::start().await;
2274 let client = logged_in_client(Some(server.uri())).await;
2275
2276 let event_id = event_id!("$2:example.org");
2277
2278 Mock::given(method("GET"))
2279 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
2280 .and(header("authorization", "Bearer 1234"))
2281 .respond_with(
2282 ResponseTemplate::new(200)
2283 .set_body_json(EventFactory::new().room_encryption().into_content()),
2284 )
2285 .mount(&server)
2286 .await;
2287
2288 Mock::given(method("PUT"))
2289 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
2290 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2291 "event_id": event_id,
2292 })))
2293 .mount(&server)
2294 .await;
2295
2296 let f = EventFactory::new().sender(user_id!("@example:localhost"));
2297 let response = SyncResponseBuilder::default()
2298 .add_joined_room(
2299 JoinedRoomBuilder::default()
2300 .add_state_event(
2301 f.member(user_id!("@example:localhost")).display_name("example"),
2302 )
2303 .add_state_event(f.default_power_levels())
2304 .add_state_event(f.room_encryption()),
2305 )
2306 .build_sync_response();
2307
2308 client.base_client().receive_sync_response(response).await.unwrap();
2309
2310 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
2311 assert!(
2312 room.latest_encryption_state().await.expect("Getting encryption state").is_encrypted()
2313 );
2314
2315 let event_id = event_id!("$1:example.org");
2316 let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
2317 room.send(reaction).await.expect("Sending the reaction should not fail");
2318
2319 room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
2320 }
2321
2322 #[cfg(feature = "sqlite")]
2323 #[async_test]
2324 async fn test_generation_counter_invalidates_olm_machine() {
2325 use matrix_sdk_base::store::RoomLoadSettings;
2328 let sqlite_path = std::env::temp_dir().join("generation_counter_sqlite.db");
2329 let session = mock_matrix_session();
2330
2331 let client1 = Client::builder()
2332 .homeserver_url("http://localhost:1234")
2333 .request_config(RequestConfig::new().disable_retry())
2334 .sqlite_store(&sqlite_path, None)
2335 .build()
2336 .await
2337 .unwrap();
2338 client1
2339 .matrix_auth()
2340 .restore_session(session.clone(), RoomLoadSettings::default())
2341 .await
2342 .unwrap();
2343
2344 let client2 = Client::builder()
2345 .homeserver_url("http://localhost:1234")
2346 .request_config(RequestConfig::new().disable_retry())
2347 .sqlite_store(sqlite_path, None)
2348 .build()
2349 .await
2350 .unwrap();
2351 client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
2352
2353 let guard = client1.encryption().try_lock_store_once().await.unwrap();
2355 assert!(guard.is_none());
2356
2357 client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2358 client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
2359
2360 let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2362 assert!(acquired1.is_some());
2363
2364 let initial_olm_machine =
2366 client1.olm_machine().await.clone().expect("must have an olm machine");
2367
2368 let decryption_key = matrix_sdk_base::crypto::store::types::BackupDecryptionKey::new();
2370 let backup_key = decryption_key.megolm_v1_public_key();
2371 backup_key.set_version("1".to_owned());
2372 initial_olm_machine
2373 .backup_machine()
2374 .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
2375 .await
2376 .expect("Should save");
2377
2378 initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
2379
2380 assert!(client1.encryption().backups().are_enabled().await);
2381
2382 let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2384 assert!(acquired2.is_none());
2385
2386 drop(acquired1);
2388 tokio::time::sleep(Duration::from_millis(100)).await;
2389
2390 let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2392 assert!(acquired1.is_some());
2393
2394 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2396 assert!(initial_olm_machine.same_as(&olm_machine));
2397
2398 drop(acquired1);
2400 tokio::time::sleep(Duration::from_millis(100)).await;
2401
2402 let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2404 assert!(acquired2.is_some());
2405
2406 drop(acquired2);
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
2417 assert!(!initial_olm_machine.same_as(&olm_machine));
2418
2419 let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
2420 assert!(backup_key_new.decryption_key.is_some());
2421 assert_eq!(
2422 backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
2423 backup_key.to_base64()
2424 );
2425 assert!(client1.encryption().backups().are_enabled().await);
2426 }
2427
2428 #[cfg(feature = "sqlite")]
2429 #[async_test]
2430 async fn test_generation_counter_no_spurious_invalidation() {
2431 use matrix_sdk_base::store::RoomLoadSettings;
2434 let sqlite_path =
2435 std::env::temp_dir().join("generation_counter_no_spurious_invalidations.db");
2436 let session = mock_matrix_session();
2437
2438 let client = Client::builder()
2439 .homeserver_url("http://localhost:1234")
2440 .request_config(RequestConfig::new().disable_retry())
2441 .sqlite_store(&sqlite_path, None)
2442 .build()
2443 .await
2444 .unwrap();
2445 client
2446 .matrix_auth()
2447 .restore_session(session.clone(), RoomLoadSettings::default())
2448 .await
2449 .unwrap();
2450
2451 let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2452
2453 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2454
2455 let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2457 assert!(initial_olm_machine.same_as(&after_enabling_lock));
2458
2459 {
2460 let client2 = Client::builder()
2462 .homeserver_url("http://localhost:1234")
2463 .request_config(RequestConfig::new().disable_retry())
2464 .sqlite_store(sqlite_path, None)
2465 .build()
2466 .await
2467 .unwrap();
2468 client2
2469 .matrix_auth()
2470 .restore_session(session, RoomLoadSettings::default())
2471 .await
2472 .unwrap();
2473
2474 client2
2475 .encryption()
2476 .enable_cross_process_store_lock("client2".to_owned())
2477 .await
2478 .unwrap();
2479
2480 let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2481 assert!(guard.is_some());
2482
2483 drop(guard);
2484 tokio::time::sleep(Duration::from_millis(100)).await;
2485 }
2486
2487 {
2488 let acquired = client.encryption().try_lock_store_once().await.unwrap();
2489 assert!(acquired.is_some());
2490 }
2491
2492 let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2494 assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2495
2496 {
2497 let acquired = client.encryption().try_lock_store_once().await.unwrap();
2498 assert!(acquired.is_some());
2499 }
2500
2501 let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2503 assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2504 }
2505
2506 #[async_test]
2507 async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2508 let client = no_retry_test_client(None).await;
2510 let server = MockServer::start().await;
2511
2512 let mut verification_state = client.encryption().verification_state();
2514
2515 assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2517
2518 let keys_requested = Arc::new(AtomicBool::new(false));
2521 let inner_bool = keys_requested.clone();
2522
2523 Mock::given(method("GET"))
2524 .and(path_regex(
2525 r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2526 ))
2527 .respond_with(move |_req: &Request| {
2528 inner_bool.fetch_or(true, Ordering::SeqCst);
2529 ResponseTemplate::new(200).set_body_json(json!({}))
2530 })
2531 .mount(&server)
2532 .await;
2533
2534 set_client_session(&client).await;
2536
2537 assert!(keys_requested.load(Ordering::SeqCst).not());
2539 assert_next_matches_with_timeout!(verification_state, VerificationState::Unverified);
2540 }
2541
2542 #[test]
2543 fn test_oauth_reset_info_from_uiaa_info() {
2544 let auth_info = json!({
2545 "session": "dummy",
2546 "flows": [
2547 {
2548 "stages": [
2549 "org.matrix.cross_signing_reset"
2550 ]
2551 }
2552 ],
2553 "params": {
2554 "org.matrix.cross_signing_reset": {
2555 "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2556 }
2557 },
2558 "msg": "To reset..."
2559 });
2560
2561 let auth_info = serde_json::from_value(auth_info)
2562 .expect("We should be able to deserialize the UiaaInfo");
2563 OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2564 .expect("We should be able to fetch the cross-signing reset info from the auth info");
2565 }
2566
2567 #[test]
2568 fn test_duplicate_one_time_key_error_parsing() {
2569 let message = concat!(
2570 r#"One time key signed_curve25519:AAAAAAAAAAA already exists. "#,
2571 r#"Old key: {"key":"dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs","#,
2572 r#""signatures":{"@example:matrix.org":{"ed25519:AAAAAAAAAA":""#,
2573 r#"Fk45zHAbrd+1j9wZXLjL2Y/+DU/Mnz9yuvlfYBOOT7qExN2Jdud+5BAuNs8nZ/caS4wTF39Kg3zQpzaGERoCBg"}}};"#,
2574 r#" new key: {'key': 'CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno', "#,
2575 r#"'signatures': {'@example:matrix.org': {'ed25519:AAAAAAAAAA': "#,
2576 r#"'BQ9Gp0p+6srF+c8OyruqKKd9R4yaub3THYAyyBB/7X/rG8BwcAqFynzl1aGyFYun4Q+087a5OSiglCXI+/kQAA'}}}"#
2577 );
2578 let message = DuplicateOneTimeKeyErrorMessage::from_str(message)
2579 .expect("We should be able to parse the error message");
2580
2581 assert_eq!(message.old_key.to_base64(), "dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs");
2582 assert_eq!(message.new_key.to_base64(), "CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno");
2583
2584 DuplicateOneTimeKeyErrorMessage::from_str("One time key already exists.")
2585 .expect_err("We shouldn't be able to parse an incomplete error message");
2586 }
2587
2588 fn devices_to_verify_against_keys_query_response(
2592 devices: Vec<serde_json::Value>,
2593 ) -> serde_json::Value {
2594 let device_keys: serde_json::Map<String, serde_json::Value> = devices
2595 .into_iter()
2596 .map(|device| (device.get("device_id").unwrap().as_str().unwrap().to_owned(), device))
2597 .collect();
2598 json!({
2599 "device_keys": {
2600 "@example:localhost": device_keys,
2601 },
2602 "master_keys": {
2603 "@example:localhost": {
2604 "keys": {
2605 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU",
2606 },
2607 "usage": ["master"],
2608 "user_id": "@example:localhost",
2609 },
2610 },
2611 "self_signing_keys": {
2612 "@example:localhost": {
2613 "keys": {
2614 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM",
2615 },
2616 "usage": ["self_signing"],
2617 "user_id": "@example:localhost",
2618 "signatures": {
2619 "@example:localhost": {
2620 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "etO1bB+rCk+TQ/FcjQ8eWu/RsRNQNNQ1Ek+PD6//j8yz6igRjfvuHZaMvr/quAFrirfgExph2TdOwlDgN5bFCQ",
2621 },
2622 },
2623 },
2624 },
2625 "user_signing_keys": {
2626 "@example:localhost": {
2627 "keys": {
2628 "ed25519:CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0": "CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0",
2629 },
2630 "usage": ["user_signing"],
2631 "user_id": "@example:localhost",
2632 "signatures": {
2633 "@example:localhost": {
2634 "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "E/DFi/hQTIb/7eSB+HbCXeTLFaLjqWHzLO9GwjL1qdhfO7ew4p6YdtXSH3T2YYr1dKCPteH/4nMYVwOhww2CBg",
2635 },
2636 },
2637 },
2638 }
2639 })
2640 }
2641
2642 #[async_test]
2645 async fn test_devices_to_verify_against_no_devices() {
2648 let server = MockServer::start().await;
2649 let client = logged_in_client(Some(server.uri())).await;
2650
2651 Mock::given(method("POST"))
2652 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2653 .respond_with(
2654 ResponseTemplate::new(200)
2655 .set_body_json(devices_to_verify_against_keys_query_response(vec![])),
2656 )
2657 .mount(&server)
2658 .await;
2659
2660 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2661 }
2662
2663 #[async_test]
2664 async fn test_devices_to_verify_against_cross_signed() {
2667 let server = MockServer::start().await;
2668 let client = logged_in_client(Some(server.uri())).await;
2669
2670 Mock::given(method("POST"))
2671 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2672 .respond_with(ResponseTemplate::new(200).set_body_json(
2673 devices_to_verify_against_keys_query_response(vec![
2674 json!({
2675 "algorithms": [
2676 "m.olm.v1.curve25519-aes-sha2",
2677 "m.megolm.v1.aes-sha2",
2678 ],
2679 "user_id": "@example:localhost",
2680 "device_id": "SIGNEDDEVICE",
2681 "keys": {
2682 "curve25519:SIGNEDDEVICE": "o1LqUtH/sqd3WF+BB2Qr77uw3sDmZhMOz68/IV9aHxs",
2683 "ed25519:SIGNEDDEVICE": "iVoEfMOoUqxXVMLdpZCOgvQuCrT3/kQWkBmB3Phi/lo",
2684 },
2685 "signatures": {
2686 "@example:localhost": {
2687 "ed25519:SIGNEDDEVICE": "C7yRu1fNrdD2EobVdtANMqk3LBtWtTRWrIU22xVS8/Om1kmA/luzek64R3N6JsZhYczVmZYBKhUC9kRvHHwOBg",
2688 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "frfh2HP28GclmGvwTic00Fj4nZCvm4RlRA6U56mnD5920hOi04+L055ojzp6ybZXvC/GQYfyTHwQXlUN1nvxBA",
2689 },
2690 },
2691 })
2692 ])
2693 ))
2694 .mount(&server)
2695 .await;
2696
2697 assert!(client.encryption().has_devices_to_verify_against().await.unwrap());
2698 }
2699
2700 #[async_test]
2701 async fn test_devices_to_verify_against_dehydrated_and_unsigned() {
2704 let server = MockServer::start().await;
2705 let client = logged_in_client(Some(server.uri())).await;
2706 let user_id = client.user_id().unwrap();
2707 let olm_machine = client.olm_machine().await;
2708 let olm_machine = olm_machine.as_ref().unwrap();
2709
2710 Mock::given(method("POST"))
2711 .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2712 .respond_with(ResponseTemplate::new(200).set_body_json(
2713 devices_to_verify_against_keys_query_response(vec![
2714 json!({
2715 "algorithms": [
2716 "m.olm.v1.curve25519-aes-sha2",
2717 "m.megolm.v1.aes-sha2",
2718 ],
2719 "user_id": "@example:localhost",
2720 "device_id": "DEHYDRATEDDEVICE",
2721 "keys": {
2722 "curve25519:DEHYDRATEDDEVICE": "XOn5VguAgokZ3p9mBz2yOB395fn6j75G8jIPcXEWQGY",
2723 "ed25519:DEHYDRATEDDEVICE": "4GG5xmBT7z4rgUgmWNlKZ+ABE3QlGgTorF+luCnKfYI",
2724 },
2725 "dehydrated": true,
2726 "signatures": {
2727 "@example:localhost": {
2728 "ed25519:DEHYDRATEDDEVICE": "+OMasB7nzVlMV+zRDxkh4h8h/Q0bY42P1SPv7X2IURIelT5G+d+AYSmg30N4maphxEDBqt/vI8/lIr71exc3Dg",
2729 "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "8DzynAgbYgXX1Md5d4Vw91Zstpoi4dpG7levFeVhi4psCAWuBnV76Qu1s2TGjQQ0CLDXEqcxxuX9X4eUK5TGCg",
2730 },
2731 },
2732 }),
2733 json!({
2734 "algorithms": [
2735 "m.olm.v1.curve25519-aes-sha2",
2736 "m.megolm.v1.aes-sha2",
2737 ],
2738 "user_id": "@example:localhost",
2739 "device_id": "UNSIGNEDDEVICE",
2740 "keys": {
2741 "curve25519:UNSIGNEDDEVICE": "mMby6NpprkHxj+ONfO9Z5lBqVUHJBMkrPFSNJhogBkg",
2742 "ed25519:UNSIGNEDDEVICE": "Zifq39ZDrlIaSRf0Hh22owEqXCPE+1JSSgs6LDlubwQ",
2743 },
2744 "signatures": {
2745 "@example:localhost": {
2746 "ed25519:UNSIGNEDDEVICE": "+L29RoDKoTufPGm/Bae65KHno7Z1H7GYhxSKpB4RQZRS7NrR29AMW1PVhEsIozYuDVEFuMZ0L8H3dlcaHxagBA",
2747 },
2748 },
2749 }),
2750 ])
2751 ))
2752 .mount(&server)
2753 .await;
2754
2755 let (request_id, request) = olm_machine.query_keys_for_users([user_id]);
2756 client.keys_query(&request_id, request.device_keys).await.unwrap();
2757
2758 assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2759 }
2760}