atagen

matrix-rust-sdk

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk.git
bindings/matrix-sdk-ffi/src/encryption.rsRust890 lines31.0 KB
1
// Copyright 2025 The Matrix.org Foundation C.I.C.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for that specific language governing permissions and
13
// limitations under the License.
14
15
use std::{str::FromStr, sync::Arc};
16
17
use futures_util::StreamExt;
18
use matrix_sdk::encryption::{self, backups, recovery};
19
use matrix_sdk_base::crypto::types::{BackupSecrets, RoomKeyBackupInfo};
20
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
21
use ruma::OwnedUserId;
22
use serde::de::Error;
23
use thiserror::Error;
24
use tracing::{error, info};
25
use zeroize::Zeroize;
26
27
use crate::{
28
    client::Client, error::ClientError, ruma::AuthData, runtime::get_runtime_handle,
29
    task_handle::TaskHandle,
30
};
31
32
#[derive(uniffi::Object)]
33
pub struct Encryption {
34
    pub(crate) inner: matrix_sdk::encryption::Encryption,
35
36
    /// A reference to the FFI client.
37
    ///
38
    /// Note: we do this to make it so that the FFI `NotificationClient` keeps
39
    /// the FFI `Client` and thus the SDK `Client` alive. Otherwise, we
40
    /// would need to repeat the hack done in the FFI `Client::drop` method.
41
    pub(crate) _client: Arc<Client>,
42
}
43
44
#[matrix_sdk_ffi_macros::export(callback_interface)]
45
pub trait BackupStateListener: SyncOutsideWasm + SendOutsideWasm {
46
    fn on_update(&self, status: BackupState);
47
}
48
49
#[matrix_sdk_ffi_macros::export(callback_interface)]
50
pub trait BackupSteadyStateListener: SyncOutsideWasm + SendOutsideWasm {
51
    fn on_update(&self, status: BackupUploadState);
52
}
53
54
#[matrix_sdk_ffi_macros::export(callback_interface)]
55
pub trait RecoveryStateListener: SyncOutsideWasm + SendOutsideWasm {
56
    fn on_update(&self, status: RecoveryState);
57
}
58
59
#[matrix_sdk_ffi_macros::export(callback_interface)]
60
pub trait VerificationStateListener: SyncOutsideWasm + SendOutsideWasm {
61
    fn on_update(&self, status: VerificationState);
62
}
63
64
#[derive(uniffi::Enum)]
65
pub enum BackupUploadState {
66
    Waiting,
67
    Uploading { backed_up_count: u32, total_count: u32 },
68
    Error,
69
    Done,
70
}
71
72
#[derive(Debug, Error, uniffi::Error)]
73
#[uniffi(flat_error)]
74
pub enum SteadyStateError {
75
    #[error("The backup got disabled while waiting for the room keys to be uploaded.")]
76
    BackupDisabled,
77
    #[error("There was a connection error.")]
78
    Connection,
79
    #[error("We couldn't read status updates from the upload task quickly enough.")]
80
    Lagged,
81
}
82
83
#[derive(Debug, Error, uniffi::Error)]
84
pub enum RecoveryError {
85
    /// A backup already exists on the homeserver, the recovery subsystem does
86
    /// not allow backups to be overwritten, disable recovery first.
87
    #[error(
88
        "A backup already exists on the homeserver and the method does not allow to overwrite it"
89
    )]
90
    BackupExistsOnServer,
91
92
    /// A typical SDK error.
93
    #[error(transparent)]
94
    Client { source: crate::ClientError },
95
96
    /// Error in the secret storage subsystem, except for when importing a
97
    /// secret.
98
    #[error("Error in the secret-storage subsystem: {error_message}")]
99
    SecretStorage { error_message: String },
100
101
    /// Error when importing a secret from secret storage.
102
    #[error("Error importing a secret: {error_message}")]
103
    Import { error_message: String },
104
}
105
106
impl From<matrix_sdk::encryption::recovery::RecoveryError> for RecoveryError {
107
    fn from(value: matrix_sdk::encryption::recovery::RecoveryError) -> Self {
108
        match value {
109
            recovery::RecoveryError::BackupExistsOnServer => Self::BackupExistsOnServer,
110
            recovery::RecoveryError::Sdk(e) => Self::Client { source: ClientError::from(e) },
111
            recovery::RecoveryError::SecretStorage(
112
                matrix_sdk::encryption::secret_storage::SecretStorageError::ImportError { .. },
113
            ) => Self::Import { error_message: value.to_string() },
114
            recovery::RecoveryError::SecretStorage(e) => {
115
                Self::SecretStorage { error_message: e.to_string() }
116
            }
117
        }
118
    }
119
}
120
121
pub type Result<A, E = RecoveryError> = std::result::Result<A, E>;
122
123
impl From<matrix_sdk::encryption::backups::futures::SteadyStateError> for SteadyStateError {
124
    fn from(value: matrix_sdk::encryption::backups::futures::SteadyStateError) -> Self {
125
        match value {
126
            backups::futures::SteadyStateError::BackupDisabled => Self::BackupDisabled,
127
            backups::futures::SteadyStateError::Connection => Self::Connection,
128
            backups::futures::SteadyStateError::Lagged => Self::Lagged,
129
        }
130
    }
131
}
132
133
#[derive(uniffi::Enum)]
134
pub enum BackupState {
135
    Unknown,
136
    Creating,
137
    Enabling,
138
    Resuming,
139
    Enabled,
140
    Downloading,
141
    Disabling,
142
}
143
144
impl From<backups::BackupState> for BackupState {
145
    fn from(value: backups::BackupState) -> Self {
146
        match value {
147
            backups::BackupState::Unknown => Self::Unknown,
148
            backups::BackupState::Creating => Self::Creating,
149
            backups::BackupState::Enabling => Self::Enabling,
150
            backups::BackupState::Resuming => Self::Resuming,
151
            backups::BackupState::Enabled => Self::Enabled,
152
            backups::BackupState::Downloading => Self::Downloading,
153
            backups::BackupState::Disabling => Self::Disabling,
154
        }
155
    }
156
}
157
158
impl From<backups::UploadState> for BackupUploadState {
159
    fn from(value: backups::UploadState) -> Self {
160
        match value {
161
            backups::UploadState::Idle => Self::Waiting,
162
            backups::UploadState::Uploading(count) => Self::Uploading {
163
                backed_up_count: count.backed_up.try_into().unwrap_or(u32::MAX),
164
                total_count: count.total.try_into().unwrap_or(u32::MAX),
165
            },
166
            backups::UploadState::Error => Self::Error,
167
            backups::UploadState::Done => Self::Done,
168
        }
169
    }
170
}
171
172
#[derive(uniffi::Enum)]
173
pub enum RecoveryState {
174
    Unknown,
175
    Enabled,
176
    Disabled,
177
    Incomplete,
178
}
179
180
impl From<recovery::RecoveryState> for RecoveryState {
181
    fn from(value: recovery::RecoveryState) -> Self {
182
        match value {
183
            recovery::RecoveryState::Unknown => Self::Unknown,
184
            recovery::RecoveryState::Enabled => Self::Enabled,
185
            recovery::RecoveryState::Disabled => Self::Disabled,
186
            recovery::RecoveryState::Incomplete => Self::Incomplete,
187
        }
188
    }
189
}
190
191
#[matrix_sdk_ffi_macros::export(callback_interface)]
192
pub trait EnableRecoveryProgressListener: SyncOutsideWasm + SendOutsideWasm {
193
    fn on_update(&self, status: EnableRecoveryProgress);
194
}
195
196
#[derive(uniffi::Enum)]
197
pub enum EnableRecoveryProgress {
198
    Starting,
199
    CreatingBackup,
200
    CreatingRecoveryKey,
201
    BackingUp { backed_up_count: u32, total_count: u32 },
202
    RoomKeyUploadError,
203
    Done { recovery_key: String },
204
}
205
206
impl From<recovery::EnableProgress> for EnableRecoveryProgress {
207
    fn from(value: recovery::EnableProgress) -> Self {
208
        match &value {
209
            recovery::EnableProgress::Starting => Self::Starting,
210
            recovery::EnableProgress::CreatingBackup => Self::CreatingBackup,
211
            recovery::EnableProgress::CreatingRecoveryKey => Self::CreatingRecoveryKey,
212
            recovery::EnableProgress::BackingUp(counts) => Self::BackingUp {
213
                backed_up_count: counts.backed_up.try_into().unwrap_or(u32::MAX),
214
                total_count: counts.backed_up.try_into().unwrap_or(u32::MAX),
215
            },
216
            recovery::EnableProgress::RoomKeyUploadError => Self::RoomKeyUploadError,
217
            recovery::EnableProgress::Done { recovery_key } => {
218
                Self::Done { recovery_key: recovery_key.to_owned() }
219
            }
220
        }
221
    }
222
}
223
224
#[derive(uniffi::Enum)]
225
pub enum VerificationState {
226
    Unknown,
227
    Verified,
228
    Unverified,
229
}
230
231
impl From<encryption::VerificationState> for VerificationState {
232
    fn from(value: encryption::VerificationState) -> Self {
233
        match &value {
234
            encryption::VerificationState::Unknown => Self::Unknown,
235
            encryption::VerificationState::Verified => Self::Verified,
236
            encryption::VerificationState::Unverified => Self::Unverified,
237
        }
238
    }
239
}
240
241
/// Struct containing the bundle of secrets to fully activate a new device for
242
/// end-to-end encryption.
243
#[derive(uniffi::Object)]
244
pub struct SecretsBundleWithUserId {
245
    user_id: OwnedUserId,
246
    inner: matrix_sdk_base::crypto::types::SecretsBundle,
247
}
248
249
/// Result for the check if a store has a valid secrets bundle.
250
#[derive(uniffi::Enum)]
251
pub enum DetectedSecretsBundle {
252
    /// The store doesn't contain a secrets bundle at all.
253
    None,
254
    /// The store contains a bundle without a backup.
255
    WithoutBackup,
256
    /// The store contains a bundle with an unused backup, the backup key in the
257
    /// bundle isn't used on the homeserver.
258
    UnusedBackup,
259
    /// The store contains a complete secrets bundle.
260
    Complete,
261
}
262
263
/// Error type describing failures that can happen while exporting a
264
/// [`SecretsBundle`] from a SQLite store.
265
#[derive(Debug, thiserror::Error, uniffi::Error)]
266
pub enum BundleExportError {
267
    /// The SQLite store couldn't be opened.
268
    #[error("the store couldn't be opened: {msg}")]
269
    OpenStoreError { msg: String },
270
    /// Data from the SQLite store couldn't be exported.
271
    #[error("the bundle couldn't be exported due to a storage error: {msg}")]
272
    StoreError { msg: String },
273
    /// The store doesn't contain a secrets bundle or it couldn't be read from
274
    /// the store.
275
    #[error("the bundle couldn't be exported: {msg}")]
276
    SecretError { msg: String },
277
    /// The store is empty and doesn't contain a secrets bundle.
278
    #[error("the store is completely empty")]
279
    StoreEmpty,
280
    /// A JSON object couldn't be deserialized while the secrets bundle was
281
    /// exported.
282
    #[error("Couldn't deserialize a JSON value: {msg}")]
283
    Json { msg: String },
284
    /// Error returned when the secrets bundle is missing a backup key or
285
    /// includes one that doesn’t match the key configured for the active backup
286
    /// version.
287
    #[error(
288
        "The bundle is missing a backup key or has one that isn't the one that's currently used"
289
    )]
290
    InvalidBackup,
291
}
292
293
#[cfg(feature = "sqlite")]
294
impl From<matrix_sdk::encryption::BundleExportError> for BundleExportError {
295
    fn from(value: matrix_sdk::encryption::BundleExportError) -> Self {
296
        match value {
297
            matrix_sdk::encryption::BundleExportError::OpenStoreError(e) => {
298
                BundleExportError::OpenStoreError { msg: e.to_string() }
299
            }
300
            matrix_sdk::encryption::BundleExportError::StoreError(e) => {
301
                BundleExportError::StoreError { msg: e.to_string() }
302
            }
303
            matrix_sdk::encryption::BundleExportError::SecretExport(e) => {
304
                BundleExportError::SecretError { msg: e.to_string() }
305
            }
306
        }
307
    }
308
}
309
310
impl From<serde_json::Error> for BundleExportError {
311
    fn from(value: serde_json::Error) -> Self {
312
        Self::Json { msg: value.to_string() }
313
    }
314
}
315
316
#[cfg(feature = "sqlite")]
317
#[matrix_sdk_ffi_macros::export]
318
impl SecretsBundleWithUserId {
319
    /// Attempt to export a [`SecretsBundle`] from a crypto store.
320
    ///
321
    /// This method can be used to retrieve a [`SecretsBundle`] from an existing
322
    /// `matrix-sdk`-based client in order to import the [`SecretsBundle`] in
323
    /// another [`Client`] instance.
324
    ///
325
    /// This can be useful for migration purposes or to allow existing client
326
    /// instances create new ones that will be fully verified.
327
    #[uniffi::constructor]
328
    pub async fn from_database(
329
        database_path: &str,
330
        mut passphrase: Option<String>,
331
        backup_info: &str,
332
    ) -> Result<Arc<Self>, BundleExportError> {
333
        let backup_info = serde_json::from_str(backup_info)?;
334
335
        let ret = if let Some((user_id, bundle)) =
336
            matrix_sdk::encryption::export_secrets_bundle_from_store(
337
                database_path,
338
                passphrase.as_deref(),
339
            )
340
            .await?
341
        {
342
            let is_backup_ok =
343
                bundle.backup.as_ref().is_some_and(|backup| is_valid_backup(backup, &backup_info));
344
345
            if is_backup_ok {
346
                Ok(SecretsBundleWithUserId { user_id, inner: bundle }.into())
347
            } else {
348
                Err(BundleExportError::InvalidBackup)
349
            }
350
        } else {
351
            Err(BundleExportError::StoreEmpty)
352
        };
353
354
        passphrase.zeroize();
355
356
        ret
357
    }
358
}
359
360
#[matrix_sdk_ffi_macros::export]
361
impl SecretsBundleWithUserId {
362
    /// Attempt to create a [`SecretsBundle`] from a previously JSON serialized
363
    /// bundle.
364
    #[uniffi::constructor]
365
    pub fn from_str(
366
        user_id: &str,
367
        bundle: &str,
368
        backup_info: &str,
369
    ) -> Result<Arc<Self>, BundleExportError> {
370
        let user_id =
371
            OwnedUserId::from_str(user_id).map_err(|e| serde_json::Error::custom(e.to_string()))?;
372
        let bundle: matrix_sdk_base::crypto::types::SecretsBundle = serde_json::from_str(bundle)?;
373
        let backup_info = serde_json::from_str(backup_info)?;
374
375
        let is_backup_ok =
376
            bundle.backup.as_ref().is_some_and(|backup| is_valid_backup(backup, &backup_info));
377
378
        if is_backup_ok {
379
            Ok(Self { user_id, inner: bundle }.into())
380
        } else {
381
            Err(BundleExportError::InvalidBackup)
382
        }
383
    }
384
385
    /// Does the bundle contain a backup key.
386
    ///
387
    /// Since enabling a backup is optional, the backup key might be missing
388
    /// from the bundle. Returns `false` if the backup key is missing,
389
    /// otherwise `true`.
390
    pub fn contains_backup_key(&self) -> bool {
391
        self.inner.backup.is_some()
392
    }
393
}
394
395
fn is_valid_backup(secrets: &BackupSecrets, info: &RoomKeyBackupInfo) -> bool {
396
    match secrets {
397
        BackupSecrets::MegolmBackupV1Curve25519AesSha2(secrets) => {
398
            secrets.key.backup_key_matches(info)
399
        }
400
    }
401
}
402
403
fn check_bundle_and_info(
404
    bundle: &matrix_sdk_base::crypto::types::SecretsBundle,
405
    info: Option<&RoomKeyBackupInfo>,
406
) -> DetectedSecretsBundle {
407
    match (&bundle.backup, info) {
408
        (None, None) => DetectedSecretsBundle::WithoutBackup,
409
        (None, Some(_)) => DetectedSecretsBundle::WithoutBackup,
410
        (Some(_), None) => DetectedSecretsBundle::UnusedBackup,
411
        (Some(backup), Some(info)) => {
412
            if is_valid_backup(backup, info) {
413
                DetectedSecretsBundle::Complete
414
            } else {
415
                DetectedSecretsBundle::UnusedBackup
416
            }
417
        }
418
    }
419
}
420
421
/// Check if a JSON encoded string contains a valid [`SecretsBundle`].
422
#[uniffi::export]
423
pub fn json_string_contains_secrets_bundle(
424
    bundle: &str,
425
    backup_info: Option<String>,
426
) -> Result<DetectedSecretsBundle, ClientError> {
427
    let info: Option<RoomKeyBackupInfo> =
428
        backup_info.map(|info| serde_json::from_str(&info)).transpose()?;
429
430
    let bundle: matrix_sdk_base::crypto::types::SecretsBundle = serde_json::from_str(bundle)?;
431
432
    Ok(check_bundle_and_info(&bundle, info.as_ref()))
433
}
434
435
/// Check if a crypto store contains a valid [`SecretsBundle`].
436
#[cfg(feature = "sqlite")]
437
#[matrix_sdk_ffi_macros::export]
438
pub async fn database_contains_secrets_bundle(
439
    database_path: &str,
440
    mut passphrase: Option<String>,
441
    backup_info: Option<String>,
442
) -> Result<DetectedSecretsBundle, BundleExportError> {
443
    let info: Option<RoomKeyBackupInfo> =
444
        backup_info.map(|info| serde_json::from_str(&info)).transpose()?;
445
446
    let maybe_bundle = matrix_sdk::encryption::export_secrets_bundle_from_store(
447
        database_path,
448
        passphrase.as_deref(),
449
    )
450
    .await?;
451
452
    passphrase.zeroize();
453
454
    Ok(match maybe_bundle {
455
        Some((_, bundle)) => check_bundle_and_info(&bundle, info.as_ref()),
456
        None => DetectedSecretsBundle::None,
457
    })
458
}
459
460
#[matrix_sdk_ffi_macros::export]
461
impl Encryption {
462
    /// Get the public ed25519 key of our own device. This is usually what is
463
    /// called the fingerprint of the device.
464
    pub async fn ed25519_key(&self) -> Option<String> {
465
        self.inner.ed25519_key().await
466
    }
467
468
    /// Get the public curve25519 key of our own device in base64. This is
469
    /// usually what is called the identity key of the device.
470
    pub async fn curve25519_key(&self) -> Option<String> {
471
        self.inner.curve25519_key().await.map(|k| k.to_base64())
472
    }
473
474
    pub fn backup_state_listener(&self, listener: Box<dyn BackupStateListener>) -> Arc<TaskHandle> {
475
        let mut stream = self.inner.backups().state_stream();
476
477
        let stream_task = TaskHandle::new(get_runtime_handle().spawn(async move {
478
            while let Some(state) = stream.next().await {
479
                let Ok(state) = state else { continue };
480
                listener.on_update(state.into());
481
            }
482
        }));
483
484
        stream_task.into()
485
    }
486
487
    pub fn backup_state(&self) -> BackupState {
488
        self.inner.backups().state().into()
489
    }
490
491
    /// Does a backup exist on the server?
492
    ///
493
    /// Because the homeserver doesn't notify us about changes to the backup
494
    /// version, the [`BackupState`] and its listener are a bit crippled.
495
    /// The `BackupState::Unknown` state might mean there is no backup at all or
496
    /// a backup exists but we don't have access to it.
497
    ///
498
    /// Therefore it is necessary to poll the server for an answer every time
499
    /// you want to differentiate between those two states.
500
    pub async fn backup_exists_on_server(&self) -> Result<bool, ClientError> {
501
        Ok(self.inner.backups().fetch_exists_on_server().await?)
502
    }
503
504
    pub fn recovery_state(&self) -> RecoveryState {
505
        self.inner.recovery().state().into()
506
    }
507
508
    pub fn recovery_state_listener(
509
        &self,
510
        listener: Box<dyn RecoveryStateListener>,
511
    ) -> Arc<TaskHandle> {
512
        let mut stream = self.inner.recovery().state_stream();
513
514
        let stream_task = TaskHandle::new(get_runtime_handle().spawn(async move {
515
            while let Some(state) = stream.next().await {
516
                listener.on_update(state.into());
517
            }
518
        }));
519
520
        stream_task.into()
521
    }
522
523
    pub async fn enable_backups(&self) -> Result<()> {
524
        Ok(self.inner.recovery().enable_backup().await?)
525
    }
526
527
    pub async fn is_last_device(&self) -> Result<bool> {
528
        Ok(self.inner.recovery().is_last_device().await?)
529
    }
530
531
    /// Does the user have other devices that the current device can verify
532
    /// against?
533
    ///
534
    /// The device must be signed by the user's cross-signing key, must have an
535
    /// identity, and must not be a dehydrated device.
536
    pub async fn has_devices_to_verify_against(&self) -> Result<bool, ClientError> {
537
        Ok(self.inner.has_devices_to_verify_against().await?)
538
    }
539
540
    pub async fn wait_for_backup_upload_steady_state(
541
        &self,
542
        progress_listener: Option<Box<dyn BackupSteadyStateListener>>,
543
    ) -> Result<(), SteadyStateError> {
544
        let backups = self.inner.backups();
545
        let wait_for_steady_state = backups.wait_for_steady_state();
546
547
        let task = if let Some(listener) = progress_listener {
548
            let mut progress_stream = wait_for_steady_state.subscribe_to_progress();
549
550
            Some(get_runtime_handle().spawn(async move {
551
                while let Some(progress) = progress_stream.next().await {
552
                    let Ok(progress) = progress else { continue };
553
                    listener.on_update(progress.into());
554
                }
555
            }))
556
        } else {
557
            None
558
        };
559
560
        let result = wait_for_steady_state.await;
561
562
        if let Some(task) = task {
563
            task.abort();
564
        }
565
566
        Ok(result?)
567
    }
568
569
    pub async fn enable_recovery(
570
        &self,
571
        wait_for_backups_to_upload: bool,
572
        mut passphrase: Option<String>,
573
        progress_listener: Box<dyn EnableRecoveryProgressListener>,
574
    ) -> Result<String> {
575
        let recovery = self.inner.recovery();
576
577
        let enable = if wait_for_backups_to_upload {
578
            recovery.enable().wait_for_backups_to_upload()
579
        } else {
580
            recovery.enable()
581
        };
582
583
        let enable = if let Some(passphrase) = &passphrase {
584
            enable.with_passphrase(passphrase)
585
        } else {
586
            enable
587
        };
588
589
        let mut progress_stream = enable.subscribe_to_progress();
590
591
        let task = get_runtime_handle().spawn(async move {
592
            while let Some(progress) = progress_stream.next().await {
593
                let Ok(progress) = progress else { continue };
594
                progress_listener.on_update(progress.into());
595
            }
596
        });
597
598
        let ret = enable.await?;
599
600
        task.abort();
601
        passphrase.zeroize();
602
603
        Ok(ret)
604
    }
605
606
    pub async fn disable_recovery(&self) -> Result<()> {
607
        Ok(self.inner.recovery().disable().await?)
608
    }
609
610
    pub async fn reset_recovery_key(&self) -> Result<String> {
611
        Ok(self.inner.recovery().reset_key().await?)
612
    }
613
614
    pub async fn recover_and_reset(&self, mut old_recovery_key: String) -> Result<String> {
615
        let result = self.inner.recovery().recover_and_reset(&old_recovery_key).await;
616
617
        old_recovery_key.zeroize();
618
619
        Ok(result?)
620
    }
621
622
    /// Completely reset the current user's crypto identity: reset the cross
623
    /// signing keys, delete the existing backup and recovery key.
624
    pub async fn reset_identity(&self) -> Result<Option<Arc<IdentityResetHandle>>, ClientError> {
625
        if let Some(reset_handle) =
626
            self.inner.recovery().reset_identity().await.map_err(ClientError::from_err)?
627
        {
628
            return Ok(Some(Arc::new(IdentityResetHandle { inner: reset_handle })));
629
        }
630
631
        Ok(None)
632
    }
633
634
    /// Download identity and key backup information from Recovery
635
    pub async fn recover(&self, mut recovery_key: String) -> Result<()> {
636
        let result = self.inner.recovery().recover(&recovery_key).await;
637
638
        recovery_key.zeroize();
639
640
        Ok(result?)
641
    }
642
643
    /// Download identity and key backup information from Recovery, and, if the
644
    /// key backup information is inconsistent, create a new key backup.
645
    ///
646
    /// This will create a new key backup if:
647
    ///
648
    /// * Key backup is enabled and the backup decryption key is missing from
649
    ///   Recovery, or
650
    /// * Key backup is enabled and the backup decryption key does not match the
651
    ///   public key
652
    pub async fn recover_and_fix_backup(&self, mut recovery_key: String) -> Result<()> {
653
        let result = self.inner.recovery().recover_and_fix_backup(&recovery_key).await;
654
655
        recovery_key.zeroize();
656
657
        Ok(result?)
658
    }
659
660
    pub fn verification_state(&self) -> VerificationState {
661
        self.inner.verification_state().get().into()
662
    }
663
664
    pub fn verification_state_listener(
665
        self: Arc<Self>,
666
        listener: Box<dyn VerificationStateListener>,
667
    ) -> Arc<TaskHandle> {
668
        let mut subscriber = self.inner.verification_state();
669
670
        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
671
            while let Some(verification_state) = subscriber.next().await {
672
                listener.on_update(verification_state.into());
673
            }
674
        })))
675
    }
676
677
    /// Waits for end-to-end encryption initialization tasks to finish, if any
678
    /// was running in the background.
679
    pub async fn wait_for_e2ee_initialization_tasks(&self) {
680
        self.inner.wait_for_e2ee_initialization_tasks().await;
681
    }
682
683
    /// Get the E2EE identity of a user.
684
    ///
685
    /// This method always tries to fetch the identity from the store, which we
686
    /// only have if the user is tracked, meaning that we are both members
687
    /// of the same encrypted room. If no user is found locally, a request will
688
    /// be made to the homeserver unless `fallback_to_server` is set to `false`.
689
    ///
690
    /// # Arguments
691
    ///
692
    /// * `user_id` - The ID of the user that the identity belongs to.
693
    /// * `fallback_to_server` - Should we request the user identity from the
694
    ///   homeserver if one isn't found locally.
695
    ///
696
    /// Returns a `UserIdentity` if one is found. Returns an error if there
697
    /// was an issue with the crypto store or with the request to the
698
    /// homeserver.
699
    ///
700
    /// This will always return `None` if the client hasn't been logged in.
701
    pub async fn user_identity(
702
        &self,
703
        user_id: String,
704
        fallback_to_server: bool,
705
    ) -> Result<Option<Arc<UserIdentity>>, ClientError> {
706
        match self.inner.get_user_identity(user_id.as_str().try_into()?).await {
707
            Ok(Some(identity)) => {
708
                return Ok(Some(Arc::new(UserIdentity { inner: identity })));
709
            }
710
            Ok(None) => {
711
                info!("No identity found in the store.");
712
            }
713
            Err(error) => {
714
                error!("Failed fetching identity from the store: {error}");
715
            }
716
        }
717
718
        info!("Requesting identity from the server.");
719
720
        if fallback_to_server {
721
            let identity = self.inner.request_user_identity(user_id.as_str().try_into()?).await?;
722
            Ok(identity.map(|identity| Arc::new(UserIdentity { inner: identity })))
723
        } else {
724
            Ok(None)
725
        }
726
    }
727
728
    /// This method will import all the private cross-signing keys and
729
    /// the private part of a backup key and its accompanying version into the
730
    /// store.
731
    ///
732
    /// Importing all the secrets will mark the device as verified and enable
733
    /// backups.
734
    ///
735
    /// **Warning**: Only import this from a trusted source, i.e. if an existing
736
    /// device is sharing this with a new device.
737
    ///
738
    /// **Warning*: Only call this method right after logging in and before the
739
    /// initial sync has been started.
740
    pub async fn import_secrets_bundle(
741
        &self,
742
        secrets_bundle: &SecretsBundleWithUserId,
743
    ) -> Result<(), ClientError> {
744
        let user_id = self._client.inner.user_id().expect(
745
            "We should have a user ID available now, this is only called once we're logged in",
746
        );
747
748
        if user_id == secrets_bundle.user_id {
749
            self.inner
750
                .import_secrets_bundle(&secrets_bundle.inner)
751
                .await
752
                .map_err(ClientError::from_err)?;
753
754
            self.inner.wait_for_e2ee_initialization_tasks().await;
755
756
            Ok(())
757
        } else {
758
            Err(ClientError::Generic {
759
                msg: "Secrets bundle does not belong to the user which was logged in".to_owned(),
760
                details: None,
761
            })
762
        }
763
    }
764
}
765
766
/// The E2EE identity of a user.
767
#[derive(uniffi::Object)]
768
pub struct UserIdentity {
769
    inner: matrix_sdk::encryption::identities::UserIdentity,
770
}
771
772
#[matrix_sdk_ffi_macros::export]
773
impl UserIdentity {
774
    /// Remember this identity, ensuring it does not result in a pin violation.
775
    ///
776
    /// When we first see a user, we assume their cryptographic identity has not
777
    /// been tampered with by the homeserver or another entity with
778
    /// man-in-the-middle capabilities. We remember this identity and call this
779
    /// action "pinning".
780
    ///
781
    /// If the identity presented for the user changes later on, the newly
782
    /// presented identity is considered to be in "pin violation". This
783
    /// method explicitly accepts the new identity, allowing it to replace
784
    /// the previously pinned one and bringing it out of pin violation.
785
    ///
786
    /// UIs should display a warning to the user when encountering an identity
787
    /// which is not verified and is in pin violation.
788
    pub(crate) async fn pin(&self) -> Result<(), ClientError> {
789
        Ok(self.inner.pin().await?)
790
    }
791
792
    /// Get the public part of the Master key of this user identity.
793
    ///
794
    /// The public part of the Master key is usually used to uniquely identify
795
    /// the identity.
796
    ///
797
    /// Returns None if the master key does not actually contain any keys.
798
    pub(crate) fn master_key(&self) -> Option<String> {
799
        self.inner.master_key().get_first_key().map(|k| k.to_base64())
800
    }
801
802
    /// Is the user identity considered to be verified.
803
    ///
804
    /// If the identity belongs to another user, our own user identity needs to
805
    /// be verified as well for the identity to be considered to be verified.
806
    pub fn is_verified(&self) -> bool {
807
        self.inner.is_verified()
808
    }
809
810
    /// True if we verified this identity at some point in the past.
811
    ///
812
    /// To reset this latch back to `false`, one must call
813
    /// [`UserIdentity::withdraw_verification()`].
814
    pub fn was_previously_verified(&self) -> bool {
815
        self.inner.was_previously_verified()
816
    }
817
818
    /// Remove the requirement for this identity to be verified.
819
    ///
820
    /// If an identity was previously verified and is not anymore it will be
821
    /// reported to the user. In order to remove this notice users have to
822
    /// verify again or to withdraw the verification requirement.
823
    pub(crate) async fn withdraw_verification(&self) -> Result<(), ClientError> {
824
        Ok(self.inner.withdraw_verification().await?)
825
    }
826
827
    /// Was this identity previously verified, and is no longer?
828
    pub fn has_verification_violation(&self) -> bool {
829
        self.inner.has_verification_violation()
830
    }
831
}
832
833
#[derive(uniffi::Object)]
834
pub struct IdentityResetHandle {
835
    pub(crate) inner: matrix_sdk::encryption::recovery::IdentityResetHandle,
836
}
837
838
#[matrix_sdk_ffi_macros::export]
839
impl IdentityResetHandle {
840
    /// Get the underlying [`CrossSigningResetAuthType`] this identity reset
841
    /// process is using.
842
    pub fn auth_type(&self) -> CrossSigningResetAuthType {
843
        self.inner.auth_type().into()
844
    }
845
846
    /// This method starts the identity reset process and
847
    /// will go through the following steps:
848
    ///
849
    /// 1. Disable backing up room keys and delete the active backup
850
    /// 2. Disable recovery and delete secret storage
851
    /// 3. Go through the cross-signing key reset flow
852
    /// 4. Finally, re-enable key backups only if they were enabled before
853
    pub async fn reset(&self, auth: Option<AuthData>) -> Result<(), ClientError> {
854
        self.inner.reset(auth.map(Into::into)).await.map_err(ClientError::from_err)
855
    }
856
857
    pub async fn cancel(&self) {
858
        self.inner.cancel().await;
859
    }
860
}
861
862
#[derive(uniffi::Enum)]
863
pub enum CrossSigningResetAuthType {
864
    /// The homeserver requires user-interactive authentication.
865
    Uiaa,
866
    /// OAuth is used for authentication and the user needs to open a URL to
867
    /// approve the upload of cross-signing keys.
868
    OAuth { info: OAuthCrossSigningResetInfo },
869
}
870
871
impl From<&matrix_sdk::encryption::CrossSigningResetAuthType> for CrossSigningResetAuthType {
872
    fn from(value: &matrix_sdk::encryption::CrossSigningResetAuthType) -> Self {
873
        match value {
874
            encryption::CrossSigningResetAuthType::Uiaa(_) => Self::Uiaa,
875
            encryption::CrossSigningResetAuthType::OAuth(info) => Self::OAuth { info: info.into() },
876
        }
877
    }
878
}
879
880
#[derive(uniffi::Record)]
881
pub struct OAuthCrossSigningResetInfo {
882
    /// The URL where the user can approve the reset of the cross-signing keys.
883
    pub approval_url: String,
884
}
885
886
impl From<&matrix_sdk::encryption::OAuthCrossSigningResetInfo> for OAuthCrossSigningResetInfo {
887
    fn from(value: &matrix_sdk::encryption::OAuthCrossSigningResetInfo) -> Self {
888
        Self { approval_url: value.approval_url.to_string() }
889
    }
890
}