Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bindings/matrix-sdk-ffi/src/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ impl SsoHandler {
let builder =
auth.login_with_sso_callback(url.into()).map_err(|_| SsoError::CallbackUrlInvalid)?;
builder.await.map_err(|_| SsoError::LoginWithTokenFailed)?;

Ok(())
}
}
Expand Down
5 changes: 5 additions & 0 deletions bindings/matrix-sdk-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,13 +488,17 @@ impl Client {
device_id: Option<String>,
) -> Result<(), ClientError> {
let mut builder = self.inner.matrix_auth().login_username(&username, &password);

if let Some(initial_device_name) = initial_device_name.as_ref() {
builder = builder.initial_device_display_name(initial_device_name);
}

if let Some(device_id) = device_id.as_ref() {
builder = builder.device_id(device_id);
}

builder.send().await?;

Ok(())
}

Expand All @@ -520,6 +524,7 @@ impl Client {
}

builder.send().await?;

Ok(())
}

Expand Down
212 changes: 207 additions & 5 deletions bindings/matrix-sdk-ffi/src/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
// See the License for that specific language governing permissions and
// limitations under the License.

use std::sync::Arc;
use std::{str::FromStr, sync::Arc};

use futures_util::StreamExt;
use matrix_sdk::{
encryption,
encryption::{backups, recovery},
};
use matrix_sdk::encryption::{self, backups, recovery};
use matrix_sdk_base::crypto::types::{BackupSecrets, RoomKeyBackupInfo};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use ruma::OwnedUserId;
use thiserror::Error;
use tracing::{error, info};
use zeroize::Zeroize;
Expand Down Expand Up @@ -238,6 +237,184 @@ impl From<encryption::VerificationState> for VerificationState {
}
}

/// Struct containing the bundle of secrets to fully activate a new devices for
/// end-to-end encryption.
#[derive(uniffi::Object)]
pub struct SecretsBundleWithUserId {
user_id: OwnedUserId,
inner: matrix_sdk_base::crypto::types::SecretsBundle,
}

/// Result for the check if a store has a valid secrets bundle.
#[derive(uniffi::Enum)]
pub enum DetectedSecretsBundle {
/// The store doesn't contain a secrets bundle at all.
None,
/// The store contains a bundle without a backup.
WithoutBackup,
/// The store contains a bundle with an unused backup, the backup key in the
/// bundle isn't used on the homeserver.
UnusedBackup,
/// The store contains a complete secrets bundle.
Complete,
}

/// Error type describing failures that can happen while exporting a
/// [`SecretsBundle`] from a SQLite store.
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum BundleExportError {
/// The SQLite store couldn't be opened.
#[error("the store coulnd't be opened: {msg}")]
OpenStoreError { msg: String },
/// Data from the SQLite store coulnd't be exported.
#[error("the bundle coulnd't be exported due to a storage error: {msg}")]
StoreError { msg: String },
/// The store doesn't contain a secrets bundle or it couldn't be read from
/// the store.
#[error("the bundle coulnt' be exported: {msg}")]
SecretError { msg: String },
/// The store is empty and doesn't contain a secrets bundle.
#[error("the store is completely empty")]
StoreEmpty,
/// A JSON object couldn't be deserialized while the secrets bundle was
/// exported.
#[error("Couldn't deserialize a JSON value: {msg}")]
Json { msg: String },
}

impl From<matrix_sdk::encryption::BundleExportError> for BundleExportError {
fn from(value: matrix_sdk::encryption::BundleExportError) -> Self {
match value {
matrix_sdk::encryption::BundleExportError::OpenStoreError(e) => {
BundleExportError::OpenStoreError { msg: e.to_string() }
}
matrix_sdk::encryption::BundleExportError::StoreError(e) => {
BundleExportError::StoreError { msg: e.to_string() }
}
matrix_sdk::encryption::BundleExportError::SecretExport(e) => {
BundleExportError::SecretError { msg: e.to_string() }
}
}
}
}

impl From<serde_json::Error> for BundleExportError {
fn from(value: serde_json::Error) -> Self {
Self::Json { msg: value.to_string() }
}
}

#[matrix_sdk_ffi_macros::export]
impl SecretsBundleWithUserId {
/// Attempt to create a [`SecretsBundle`] from a previously JSON serialized
/// bundle.
#[uniffi::constructor]
pub fn from_str(
user_id: &str,
bundle: &str,
backup_info: &str,
) -> Result<Arc<Self>, ClientError> {
let user_id = OwnedUserId::from_str(user_id)?;
let mut bundle: matrix_sdk_base::crypto::types::SecretsBundle =
serde_json::from_str(bundle)?;
let backup_info = serde_json::from_str(&backup_info)?;

let should_remove_backup =
bundle.backup.as_ref().is_some_and(|backup| !is_valid_backup(backup, &backup_info));

if should_remove_backup {
bundle.backup = None;
}

Ok(Self { user_id, inner: bundle }.into())
}

/// Attempt to export a [`SecretsBundle`] from a crypto store.
///
/// This method can be used to retrieve a [`SecretsBundle`] from an existing
/// `matrix-sdk`-based client in order to import the [`SecretsBundle`] in
/// another [`Client`] instance.
///
/// This can be useful for migration purposes or to allow existing client
/// instances create new ones that will be fully verified.
#[uniffi::constructor]
pub async fn from_database(
database_path: &str,
mut passphrase: Option<String>,
backup_info: &str,
) -> Result<Arc<Self>, BundleExportError> {
let backup_info = serde_json::from_str(&backup_info)?;

let ret = if let Some((user_id, mut inner)) =
matrix_sdk::encryption::export_secrets_bundle_from_store(
database_path,
passphrase.as_deref(),
)
.await?
{
let should_remove_backup =
inner.backup.as_ref().is_some_and(|backup| !is_valid_backup(backup, &backup_info));

if should_remove_backup {
inner.backup = None;
}

Ok(SecretsBundleWithUserId { user_id, inner }.into())
} else {
Err(BundleExportError::StoreEmpty)
};

passphrase.zeroize();

ret
}

/// Does the bundle contain a backup key.
///
/// Since enabling a backup is optional, the backup key might be missing
/// from the bundle. Returns `false` if the backup key is missing,
/// otherwise `true`.
pub fn contains_backup_key(&self) -> bool {
self.inner.backup.is_some()
}
}

fn is_valid_backup(secrets: &BackupSecrets, info: &RoomKeyBackupInfo) -> bool {
match secrets {
BackupSecrets::MegolmBackupV1Curve25519AesSha2(secrets) => {
secrets.key.backup_key_matches(info)
}
}
}

/// Check if a crypto store contains a valid [`SecretsBundle`].
#[matrix_sdk_ffi_macros::export]
pub async fn database_contains_secrets_bundle(
database_path: &str,
mut passphrase: Option<String>,
backup_info: String,
) -> Result<DetectedSecretsBundle, ClientError> {
let info = serde_json::from_str(&backup_info)?;

let maybe_bundle = matrix_sdk::encryption::export_secrets_bundle_from_store(
database_path,
passphrase.as_deref(),
)
.await
.map_err(ClientError::from_err)?;

passphrase.zeroize();

Ok(match maybe_bundle {
Some((_, bundle)) => match &bundle.backup {
Some(backup) if is_valid_backup(backup, &info) => DetectedSecretsBundle::Complete,
Some(_) => DetectedSecretsBundle::UnusedBackup,
None => DetectedSecretsBundle::WithoutBackup,
},
None => DetectedSecretsBundle::None,
})
}

#[matrix_sdk_ffi_macros::export]
impl Encryption {
/// Get the public ed25519 key of our own device. This is usually what is
Expand Down Expand Up @@ -505,6 +682,31 @@ impl Encryption {
Ok(None)
}
}

pub async fn import_secrets_bundle(
&self,
secrets_bundle: &SecretsBundleWithUserId,
) -> Result<(), ClientError> {
let user_id = self._client.inner.user_id().expect(
"We should have a user ID available now, this is only called once we're logged in",
);

if user_id == secrets_bundle.user_id {
self.inner
.import_secrets_bundle(&secrets_bundle.inner)
.await
.map_err(ClientError::from_err)?;

self.inner.wait_for_e2ee_initialization_tasks().await;

Ok(())
} else {
Err(ClientError::Generic {
msg: "Secrets bundle does not belong to the user which was logged in".to_owned(),
details: None,
})
}
}
}

/// The E2EE identity of a user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ async fn finish_login<Q>(

// Import the secrets bundle, this will allow us to sign the device keys with
// the master key when we upload them.
client.encryption().import_secrets_bundle(&bundle).await?;
client.encryption().import_secrets_bundle_impl(&bundle).await?;

// Upload the device keys, this will ensure that other devices see us as a fully
// verified device ass soon as this method returns.
Expand Down
2 changes: 1 addition & 1 deletion crates/matrix-sdk/src/encryption/backups/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,7 @@ impl Backups {
}

/// Check and re-enable a backup if we have a backup recovery key locally.
async fn maybe_resume_backups(&self) -> Result<(), Error> {
pub(super) async fn maybe_resume_backups(&self) -> Result<(), Error> {
let olm_machine = self.client.olm_machine().await;
let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;

Expand Down
Loading
Loading