matrix_sdk_ffi/
encryption.rs

1use std::sync::Arc;
2
3use futures_util::StreamExt;
4use matrix_sdk::{
5    encryption,
6    encryption::{backups, recovery},
7};
8use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
9use thiserror::Error;
10use tracing::{error, info};
11use zeroize::Zeroize;
12
13use crate::{
14    client::Client, error::ClientError, ruma::AuthData, runtime::get_runtime_handle,
15    task_handle::TaskHandle,
16};
17
18#[derive(uniffi::Object)]
19pub struct Encryption {
20    pub(crate) inner: matrix_sdk::encryption::Encryption,
21
22    /// A reference to the FFI client.
23    ///
24    /// Note: we do this to make it so that the FFI `NotificationClient` keeps
25    /// the FFI `Client` and thus the SDK `Client` alive. Otherwise, we
26    /// would need to repeat the hack done in the FFI `Client::drop` method.
27    pub(crate) _client: Arc<Client>,
28}
29
30#[matrix_sdk_ffi_macros::export(callback_interface)]
31pub trait BackupStateListener: SyncOutsideWasm + SendOutsideWasm {
32    fn on_update(&self, status: BackupState);
33}
34
35#[matrix_sdk_ffi_macros::export(callback_interface)]
36pub trait BackupSteadyStateListener: SyncOutsideWasm + SendOutsideWasm {
37    fn on_update(&self, status: BackupUploadState);
38}
39
40#[matrix_sdk_ffi_macros::export(callback_interface)]
41pub trait RecoveryStateListener: SyncOutsideWasm + SendOutsideWasm {
42    fn on_update(&self, status: RecoveryState);
43}
44
45#[matrix_sdk_ffi_macros::export(callback_interface)]
46pub trait VerificationStateListener: SyncOutsideWasm + SendOutsideWasm {
47    fn on_update(&self, status: VerificationState);
48}
49
50#[derive(uniffi::Enum)]
51pub enum BackupUploadState {
52    Waiting,
53    Uploading { backed_up_count: u32, total_count: u32 },
54    Error,
55    Done,
56}
57
58#[derive(Debug, Error, uniffi::Error)]
59#[uniffi(flat_error)]
60pub enum SteadyStateError {
61    #[error("The backup got disabled while waiting for the room keys to be uploaded.")]
62    BackupDisabled,
63    #[error("There was a connection error.")]
64    Connection,
65    #[error("We couldn't read status updates from the upload task quickly enough.")]
66    Lagged,
67}
68
69#[derive(Debug, Error, uniffi::Error)]
70pub enum RecoveryError {
71    /// A backup already exists on the homeserver, the recovery subsystem does
72    /// not allow backups to be overwritten, disable recovery first.
73    #[error(
74        "A backup already exists on the homeserver and the method does not allow to overwrite it"
75    )]
76    BackupExistsOnServer,
77
78    /// A typical SDK error.
79    #[error(transparent)]
80    Client { source: crate::ClientError },
81
82    /// Error in the secret storage subsystem.
83    #[error("Error in the secret-storage subsystem: {error_message}")]
84    SecretStorage { error_message: String },
85}
86
87impl From<matrix_sdk::encryption::recovery::RecoveryError> for RecoveryError {
88    fn from(value: matrix_sdk::encryption::recovery::RecoveryError) -> Self {
89        match value {
90            recovery::RecoveryError::BackupExistsOnServer => Self::BackupExistsOnServer,
91            recovery::RecoveryError::Sdk(e) => Self::Client { source: ClientError::from(e) },
92            recovery::RecoveryError::SecretStorage(e) => {
93                Self::SecretStorage { error_message: e.to_string() }
94            }
95        }
96    }
97}
98
99pub type Result<A, E = RecoveryError> = std::result::Result<A, E>;
100
101impl From<matrix_sdk::encryption::backups::futures::SteadyStateError> for SteadyStateError {
102    fn from(value: matrix_sdk::encryption::backups::futures::SteadyStateError) -> Self {
103        match value {
104            backups::futures::SteadyStateError::BackupDisabled => Self::BackupDisabled,
105            backups::futures::SteadyStateError::Connection => Self::Connection,
106            backups::futures::SteadyStateError::Lagged => Self::Lagged,
107        }
108    }
109}
110
111#[derive(uniffi::Enum)]
112pub enum BackupState {
113    Unknown,
114    Creating,
115    Enabling,
116    Resuming,
117    Enabled,
118    Downloading,
119    Disabling,
120}
121
122impl From<backups::BackupState> for BackupState {
123    fn from(value: backups::BackupState) -> Self {
124        match value {
125            backups::BackupState::Unknown => Self::Unknown,
126            backups::BackupState::Creating => Self::Creating,
127            backups::BackupState::Enabling => Self::Enabling,
128            backups::BackupState::Resuming => Self::Resuming,
129            backups::BackupState::Enabled => Self::Enabled,
130            backups::BackupState::Downloading => Self::Downloading,
131            backups::BackupState::Disabling => Self::Disabling,
132        }
133    }
134}
135
136impl From<backups::UploadState> for BackupUploadState {
137    fn from(value: backups::UploadState) -> Self {
138        match value {
139            backups::UploadState::Idle => Self::Waiting,
140            backups::UploadState::Uploading(count) => Self::Uploading {
141                backed_up_count: count.backed_up.try_into().unwrap_or(u32::MAX),
142                total_count: count.total.try_into().unwrap_or(u32::MAX),
143            },
144            backups::UploadState::Error => Self::Error,
145            backups::UploadState::Done => Self::Done,
146        }
147    }
148}
149
150#[derive(uniffi::Enum)]
151pub enum RecoveryState {
152    Unknown,
153    Enabled,
154    Disabled,
155    Incomplete,
156}
157
158impl From<recovery::RecoveryState> for RecoveryState {
159    fn from(value: recovery::RecoveryState) -> Self {
160        match value {
161            recovery::RecoveryState::Unknown => Self::Unknown,
162            recovery::RecoveryState::Enabled => Self::Enabled,
163            recovery::RecoveryState::Disabled => Self::Disabled,
164            recovery::RecoveryState::Incomplete => Self::Incomplete,
165        }
166    }
167}
168
169#[matrix_sdk_ffi_macros::export(callback_interface)]
170pub trait EnableRecoveryProgressListener: SyncOutsideWasm + SendOutsideWasm {
171    fn on_update(&self, status: EnableRecoveryProgress);
172}
173
174#[derive(uniffi::Enum)]
175pub enum EnableRecoveryProgress {
176    Starting,
177    CreatingBackup,
178    CreatingRecoveryKey,
179    BackingUp { backed_up_count: u32, total_count: u32 },
180    RoomKeyUploadError,
181    Done { recovery_key: String },
182}
183
184impl From<recovery::EnableProgress> for EnableRecoveryProgress {
185    fn from(value: recovery::EnableProgress) -> Self {
186        match &value {
187            recovery::EnableProgress::Starting => Self::Starting,
188            recovery::EnableProgress::CreatingBackup => Self::CreatingBackup,
189            recovery::EnableProgress::CreatingRecoveryKey => Self::CreatingRecoveryKey,
190            recovery::EnableProgress::BackingUp(counts) => Self::BackingUp {
191                backed_up_count: counts.backed_up.try_into().unwrap_or(u32::MAX),
192                total_count: counts.backed_up.try_into().unwrap_or(u32::MAX),
193            },
194            recovery::EnableProgress::RoomKeyUploadError => Self::RoomKeyUploadError,
195            recovery::EnableProgress::Done { recovery_key } => {
196                Self::Done { recovery_key: recovery_key.to_owned() }
197            }
198        }
199    }
200}
201
202#[derive(uniffi::Enum)]
203pub enum VerificationState {
204    Unknown,
205    Verified,
206    Unverified,
207}
208
209impl From<encryption::VerificationState> for VerificationState {
210    fn from(value: encryption::VerificationState) -> Self {
211        match &value {
212            encryption::VerificationState::Unknown => Self::Unknown,
213            encryption::VerificationState::Verified => Self::Verified,
214            encryption::VerificationState::Unverified => Self::Unverified,
215        }
216    }
217}
218
219#[matrix_sdk_ffi_macros::export]
220impl Encryption {
221    /// Get the public ed25519 key of our own device. This is usually what is
222    /// called the fingerprint of the device.
223    pub async fn ed25519_key(&self) -> Option<String> {
224        self.inner.ed25519_key().await
225    }
226
227    /// Get the public curve25519 key of our own device in base64. This is
228    /// usually what is called the identity key of the device.
229    pub async fn curve25519_key(&self) -> Option<String> {
230        self.inner.curve25519_key().await.map(|k| k.to_base64())
231    }
232
233    pub fn backup_state_listener(&self, listener: Box<dyn BackupStateListener>) -> Arc<TaskHandle> {
234        let mut stream = self.inner.backups().state_stream();
235
236        let stream_task = TaskHandle::new(get_runtime_handle().spawn(async move {
237            while let Some(state) = stream.next().await {
238                let Ok(state) = state else { continue };
239                listener.on_update(state.into());
240            }
241        }));
242
243        stream_task.into()
244    }
245
246    pub fn backup_state(&self) -> BackupState {
247        self.inner.backups().state().into()
248    }
249
250    /// Does a backup exist on the server?
251    ///
252    /// Because the homeserver doesn't notify us about changes to the backup
253    /// version, the [`BackupState`] and its listener are a bit crippled.
254    /// The `BackupState::Unknown` state might mean there is no backup at all or
255    /// a backup exists but we don't have access to it.
256    ///
257    /// Therefore it is necessary to poll the server for an answer every time
258    /// you want to differentiate between those two states.
259    pub async fn backup_exists_on_server(&self) -> Result<bool, ClientError> {
260        Ok(self.inner.backups().fetch_exists_on_server().await?)
261    }
262
263    pub fn recovery_state(&self) -> RecoveryState {
264        self.inner.recovery().state().into()
265    }
266
267    pub fn recovery_state_listener(
268        &self,
269        listener: Box<dyn RecoveryStateListener>,
270    ) -> Arc<TaskHandle> {
271        let mut stream = self.inner.recovery().state_stream();
272
273        let stream_task = TaskHandle::new(get_runtime_handle().spawn(async move {
274            while let Some(state) = stream.next().await {
275                listener.on_update(state.into());
276            }
277        }));
278
279        stream_task.into()
280    }
281
282    pub async fn enable_backups(&self) -> Result<()> {
283        Ok(self.inner.recovery().enable_backup().await?)
284    }
285
286    pub async fn is_last_device(&self) -> Result<bool> {
287        Ok(self.inner.recovery().is_last_device().await?)
288    }
289
290    pub async fn wait_for_backup_upload_steady_state(
291        &self,
292        progress_listener: Option<Box<dyn BackupSteadyStateListener>>,
293    ) -> Result<(), SteadyStateError> {
294        let backups = self.inner.backups();
295        let wait_for_steady_state = backups.wait_for_steady_state();
296
297        let task = if let Some(listener) = progress_listener {
298            let mut progress_stream = wait_for_steady_state.subscribe_to_progress();
299
300            Some(get_runtime_handle().spawn(async move {
301                while let Some(progress) = progress_stream.next().await {
302                    let Ok(progress) = progress else { continue };
303                    listener.on_update(progress.into());
304                }
305            }))
306        } else {
307            None
308        };
309
310        let result = wait_for_steady_state.await;
311
312        if let Some(task) = task {
313            task.abort();
314        }
315
316        Ok(result?)
317    }
318
319    pub async fn enable_recovery(
320        &self,
321        wait_for_backups_to_upload: bool,
322        mut passphrase: Option<String>,
323        progress_listener: Box<dyn EnableRecoveryProgressListener>,
324    ) -> Result<String> {
325        let recovery = self.inner.recovery();
326
327        let enable = if wait_for_backups_to_upload {
328            recovery.enable().wait_for_backups_to_upload()
329        } else {
330            recovery.enable()
331        };
332
333        let enable = if let Some(passphrase) = &passphrase {
334            enable.with_passphrase(passphrase)
335        } else {
336            enable
337        };
338
339        let mut progress_stream = enable.subscribe_to_progress();
340
341        let task = get_runtime_handle().spawn(async move {
342            while let Some(progress) = progress_stream.next().await {
343                let Ok(progress) = progress else { continue };
344                progress_listener.on_update(progress.into());
345            }
346        });
347
348        let ret = enable.await?;
349
350        task.abort();
351        passphrase.zeroize();
352
353        Ok(ret)
354    }
355
356    pub async fn disable_recovery(&self) -> Result<()> {
357        Ok(self.inner.recovery().disable().await?)
358    }
359
360    pub async fn reset_recovery_key(&self) -> Result<String> {
361        Ok(self.inner.recovery().reset_key().await?)
362    }
363
364    pub async fn recover_and_reset(&self, mut old_recovery_key: String) -> Result<String> {
365        let result = self.inner.recovery().recover_and_reset(&old_recovery_key).await;
366
367        old_recovery_key.zeroize();
368
369        Ok(result?)
370    }
371
372    /// Completely reset the current user's crypto identity: reset the cross
373    /// signing keys, delete the existing backup and recovery key.
374    pub async fn reset_identity(&self) -> Result<Option<Arc<IdentityResetHandle>>, ClientError> {
375        if let Some(reset_handle) =
376            self.inner.recovery().reset_identity().await.map_err(ClientError::from_err)?
377        {
378            return Ok(Some(Arc::new(IdentityResetHandle { inner: reset_handle })));
379        }
380
381        Ok(None)
382    }
383
384    pub async fn recover(&self, mut recovery_key: String) -> Result<()> {
385        let result = self.inner.recovery().recover(&recovery_key).await;
386
387        recovery_key.zeroize();
388
389        Ok(result?)
390    }
391
392    pub fn verification_state(&self) -> VerificationState {
393        self.inner.verification_state().get().into()
394    }
395
396    pub fn verification_state_listener(
397        self: Arc<Self>,
398        listener: Box<dyn VerificationStateListener>,
399    ) -> Arc<TaskHandle> {
400        let mut subscriber = self.inner.verification_state();
401
402        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
403            while let Some(verification_state) = subscriber.next().await {
404                listener.on_update(verification_state.into());
405            }
406        })))
407    }
408
409    /// Waits for end-to-end encryption initialization tasks to finish, if any
410    /// was running in the background.
411    pub async fn wait_for_e2ee_initialization_tasks(&self) {
412        self.inner.wait_for_e2ee_initialization_tasks().await;
413    }
414
415    /// Get the E2EE identity of a user.
416    ///
417    /// This method always tries to fetch the identity from the store, which we
418    /// only have if the user is tracked, meaning that we are both members
419    /// of the same encrypted room. If no user is found locally, a request will
420    /// be made to the homeserver.
421    ///
422    /// # Arguments
423    ///
424    /// * `user_id` - The ID of the user that the identity belongs to.
425    ///
426    /// Returns a `UserIdentity` if one is found. Returns an error if there
427    /// was an issue with the crypto store or with the request to the
428    /// homeserver.
429    ///
430    /// This will always return `None` if the client hasn't been logged in.
431    pub async fn user_identity(
432        &self,
433        user_id: String,
434    ) -> Result<Option<Arc<UserIdentity>>, ClientError> {
435        match self.inner.get_user_identity(user_id.as_str().try_into()?).await {
436            Ok(Some(identity)) => {
437                return Ok(Some(Arc::new(UserIdentity { inner: identity })));
438            }
439            Ok(None) => {
440                info!("No identity found in the store.");
441            }
442            Err(error) => {
443                error!("Failed fetching identity from the store: {error}");
444            }
445        }
446
447        info!("Requesting identity from the server.");
448
449        let identity = self.inner.request_user_identity(user_id.as_str().try_into()?).await?;
450        Ok(identity.map(|identity| Arc::new(UserIdentity { inner: identity })))
451    }
452}
453
454/// The E2EE identity of a user.
455#[derive(uniffi::Object)]
456pub struct UserIdentity {
457    inner: matrix_sdk::encryption::identities::UserIdentity,
458}
459
460#[matrix_sdk_ffi_macros::export]
461impl UserIdentity {
462    /// Remember this identity, ensuring it does not result in a pin violation.
463    ///
464    /// When we first see a user, we assume their cryptographic identity has not
465    /// been tampered with by the homeserver or another entity with
466    /// man-in-the-middle capabilities. We remember this identity and call this
467    /// action "pinning".
468    ///
469    /// If the identity presented for the user changes later on, the newly
470    /// presented identity is considered to be in "pin violation". This
471    /// method explicitly accepts the new identity, allowing it to replace
472    /// the previously pinned one and bringing it out of pin violation.
473    ///
474    /// UIs should display a warning to the user when encountering an identity
475    /// which is not verified and is in pin violation.
476    pub(crate) async fn pin(&self) -> Result<(), ClientError> {
477        Ok(self.inner.pin().await?)
478    }
479
480    /// Get the public part of the Master key of this user identity.
481    ///
482    /// The public part of the Master key is usually used to uniquely identify
483    /// the identity.
484    ///
485    /// Returns None if the master key does not actually contain any keys.
486    pub(crate) fn master_key(&self) -> Option<String> {
487        self.inner.master_key().get_first_key().map(|k| k.to_base64())
488    }
489
490    /// Is the user identity considered to be verified.
491    ///
492    /// If the identity belongs to another user, our own user identity needs to
493    /// be verified as well for the identity to be considered to be verified.
494    pub fn is_verified(&self) -> bool {
495        self.inner.is_verified()
496    }
497
498    /// True if we verified this identity at some point in the past.
499    ///
500    /// To reset this latch back to `false`, one must call
501    /// [`UserIdentity::withdraw_verification()`].
502    pub fn was_previously_verified(&self) -> bool {
503        self.inner.was_previously_verified()
504    }
505
506    /// Remove the requirement for this identity to be verified.
507    ///
508    /// If an identity was previously verified and is not anymore it will be
509    /// reported to the user. In order to remove this notice users have to
510    /// verify again or to withdraw the verification requirement.
511    pub(crate) async fn withdraw_verification(&self) -> Result<(), ClientError> {
512        Ok(self.inner.withdraw_verification().await?)
513    }
514
515    /// Was this identity previously verified, and is no longer?
516    pub fn has_verification_violation(&self) -> bool {
517        self.inner.has_verification_violation()
518    }
519}
520
521#[derive(uniffi::Object)]
522pub struct IdentityResetHandle {
523    pub(crate) inner: matrix_sdk::encryption::recovery::IdentityResetHandle,
524}
525
526#[matrix_sdk_ffi_macros::export]
527impl IdentityResetHandle {
528    /// Get the underlying [`CrossSigningResetAuthType`] this identity reset
529    /// process is using.
530    pub fn auth_type(&self) -> CrossSigningResetAuthType {
531        self.inner.auth_type().into()
532    }
533
534    /// This method starts the identity reset process and
535    /// will go through the following steps:
536    ///
537    /// 1. Disable backing up room keys and delete the active backup
538    /// 2. Disable recovery and delete secret storage
539    /// 3. Go through the cross-signing key reset flow
540    /// 4. Finally, re-enable key backups only if they were enabled before
541    pub async fn reset(&self, auth: Option<AuthData>) -> Result<(), ClientError> {
542        if let Some(auth) = auth {
543            self.inner.reset(Some(auth.into())).await.map_err(ClientError::from_err)
544        } else {
545            self.inner.reset(None).await.map_err(ClientError::from_err)
546        }
547    }
548
549    pub async fn cancel(&self) {
550        self.inner.cancel().await;
551    }
552}
553
554#[derive(uniffi::Enum)]
555pub enum CrossSigningResetAuthType {
556    /// The homeserver requires user-interactive authentication.
557    Uiaa,
558    // /// OIDC is used for authentication and the user needs to open a URL to
559    // /// approve the upload of cross-signing keys.
560    Oidc {
561        info: OidcCrossSigningResetInfo,
562    },
563}
564
565impl From<&matrix_sdk::encryption::CrossSigningResetAuthType> for CrossSigningResetAuthType {
566    fn from(value: &matrix_sdk::encryption::CrossSigningResetAuthType) -> Self {
567        match value {
568            encryption::CrossSigningResetAuthType::Uiaa(_) => Self::Uiaa,
569            encryption::CrossSigningResetAuthType::OAuth(info) => Self::Oidc { info: info.into() },
570        }
571    }
572}
573
574#[derive(uniffi::Record)]
575pub struct OidcCrossSigningResetInfo {
576    /// The URL where the user can approve the reset of the cross-signing keys.
577    pub approval_url: String,
578}
579
580impl From<&matrix_sdk::encryption::OAuthCrossSigningResetInfo> for OidcCrossSigningResetInfo {
581    fn from(value: &matrix_sdk::encryption::OAuthCrossSigningResetInfo) -> Self {
582        Self { approval_url: value.approval_url.to_string() }
583    }
584}