Skip to content

Expose QR login related structs and methods #118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 21, 2024
Merged
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# UNRELEASED

- Add `OlmMachine.importSecretsBundle()` and `OlmMachine.exportSecretsBundle()`
methods as well as the `SecretsBundle` class to import end-to-end encryption
secrets in a bundled manner.

- Expose the vodozemac ECIES support, which can be used to establish the secure
channel required for QR code login described in [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108).

- Add `QrCodeData` and `QrCodeMode` classes which can be used to parse or
generate QR codes intended for the QR code login mechanism described in
[MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108).

- Update matrix-rust-sdk to `35173347f`, which includes:

- Add data types to parse the QR code data for the QR code login defined in
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ serde_json = "1.0.91"
serde-wasm-bindgen = "0.5.0"
tracing = { version = "0.1.36", default-features = false, features = ["std"] }
tracing-subscriber = { version = "0.3.14", default-features = false, features = ["registry", "std", "ansi"] }
url = "2.5.0"
wasm-bindgen = "0.2.89"
wasm-bindgen-futures = "0.4.33"
zeroize = "1.6.0"
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub mod libolm_migration;
pub mod machine;
mod macros;
pub mod olm;
pub mod qr_login;
pub mod requests;
pub mod responses;
pub mod store;
Expand Down
43 changes: 43 additions & 0 deletions src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,49 @@ impl OlmMachine {
})
}

/// Export all the secrets we have in the store into a [`SecretsBundle`].
///
/// This method will export all the private cross-signing keys and, if
/// available, the private part of a backup key and its accompanying
/// version.
///
/// The method will fail if we don't have all three private cross-signing
/// keys available.
///
/// **Warning**: Only export this and share it with a trusted recipient,
/// i.e. if an existing device is sharing this with a new device.
#[wasm_bindgen(js_name = "exportSecretsBundle")]
pub fn export_secrets_bundle(&self) -> Promise {
let me = self.inner.clone();

future_to_promise(async move {
Ok(me.store().export_secrets_bundle().await.map(store::SecretsBundle::from)?)
})
}

/// Import and persists secrets from a [`SecretsBundle`].
///
/// This method will import all the private cross-signing keys and, if
/// available, the private part of a backup key and its accompanying
/// version into the store.
///
/// **Warning**: Only import this from a trusted source, i.e. if an existing
/// device is sharing this with a new device. The imported cross-signing
/// keys will create a [`OwnUserIdentity`] and mark it as verified.
///
/// The backup key will be persisted in the store and can be enabled using
/// the [`BackupMachine`].
#[wasm_bindgen(js_name = "importSecretsBundle")]
pub fn import_secrets_bundle(&self, bundle: store::SecretsBundle) -> Promise {
let me = self.inner.clone();

future_to_promise(async move {
me.store().import_secrets_bundle(&bundle.inner).await?;

Ok(JsValue::null())
})
}

/// Export all the private cross signing keys we have.
///
/// The export will contain the seeds for the ed25519 keys as
Expand Down
141 changes: 141 additions & 0 deletions src/qr_login.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! Types for QR code login

use matrix_sdk_crypto::types::qr_login;
use url::Url;
use wasm_bindgen::prelude::*;

use crate::vodozemac::Curve25519PublicKey;

/// The mode of the QR code login.
///
/// The QR code login mechanism supports both, the new device, as well as the
/// existing device to display the QR code.
///
/// The different modes have an explicit one-byte identifier which gets added to
/// the QR code data.
#[wasm_bindgen]
#[derive(Debug)]
pub enum QrCodeMode {
/// The new device is displaying the QR code.
Login,
/// The existing device is displaying the QR code.
Reciprocate,
}

impl From<qr_login::QrCodeMode> for QrCodeMode {
fn from(value: qr_login::QrCodeMode) -> Self {
match value {
qr_login::QrCodeMode::Login => Self::Login,
qr_login::QrCodeMode::Reciprocate => Self::Reciprocate,
}
}
}

/// Data for the QR code login mechanism.
///
/// The [`QrCodeData`] can be serialized and encoded as a QR code or it can be
/// decoded from a QR code.
#[wasm_bindgen]
#[derive(Debug)]
pub struct QrCodeData {
inner: qr_login::QrCodeData,
}

#[wasm_bindgen]
impl QrCodeData {
/// Create new [`QrCodeData`] from a given public key, a rendezvous URL and,
/// optionally, a homeserver URL.
///
/// If a homeserver URL is given, then the [`QrCodeData`] mode will be
/// [`QrCodeMode::Reciprocate`], i.e. the QR code will contain data for the
/// existing device to display the QR code.
///
/// If no homeserver is given, the [`QrCodeData`] mode will be
/// [`QrCodeMode::Login`], i.e. the QR code will contain data for the
/// new device to display the QR code.
Comment on lines +46 to +55
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please bear in mind that [`QrCodeData`] doesn't work in the documentation generated by wasm_bindgen: see https://matrix-org.github.io/matrix-rust-sdk-crypto-wasm/classes/QrCodeData.html#constructor.

You need to use tsdoc-style {@link ... } links.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the style in: 2c9bd6f.

#[wasm_bindgen(constructor)]
pub fn new(
public_key: Curve25519PublicKey,
rendezvous_url: &str,
homeserver_url: Option<String>,
) -> Result<QrCodeData, JsError> {
let public_key = public_key.inner;
let rendezvous_url = Url::parse(rendezvous_url)?;

let mode_data = if let Some(homeserver_url) = homeserver_url {
qr_login::QrCodeModeData::Reciprocate { homeserver_url: Url::parse(&homeserver_url)? }
} else {
qr_login::QrCodeModeData::Login
};

let inner = qr_login::QrCodeData { public_key, rendezvous_url, mode_data };

Ok(QrCodeData { inner })
}

/// Attempt to decode a slice of bytes into a [`QrCodeData`] object.
///
/// The slice of bytes would generally be returned by a QR code decoder.
pub fn from_bytes(bytes: &[u8]) -> Result<QrCodeData, JsError> {
Ok(Self { inner: qr_login::QrCodeData::from_bytes(bytes)? })
}

/// Encode the [`QrCodeData`] into a list of bytes.
///
/// The list of bytes can be used by a QR code generator to create an image
/// containing a QR code.
pub fn to_bytes(&self) -> Vec<u8> {
self.inner.to_bytes()
}

/// Attempt to decode a base64 encoded string into a [`QrCodeData`] object.
pub fn from_base64(data: &str) -> Result<QrCodeData, JsError> {
Ok(Self { inner: qr_login::QrCodeData::from_base64(data)? })
}

/// Encode the [`QrCodeData`] into a string using base64.
///
/// This format can be used for debugging purposes and the
/// [`QrcodeData::from_base64()`] method can be used to parse the string
/// again.
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}

/// Get the Curve25519 public key embedded in the [`QrCodeData`].
///
/// This Curve25519 public key should be used to establish an
/// [ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme)
/// (Elliptic Curve Integrated Encryption Scheme) channel with the other
/// device.
#[wasm_bindgen(getter)]
pub fn public_key(&self) -> Curve25519PublicKey {
self.inner.public_key.into()
}

/// Get the URL of the rendezvous server which will be used to exchange
/// messages between the two devices.
#[wasm_bindgen(getter)]
pub fn rendezvous_url(&self) -> String {
self.inner.rendezvous_url.as_str().to_owned()
}

/// Get the homeserver URL which the new device will be logged in to.
///
/// This will be only available if the existing device has generated the QR
/// code and the new device is the one scanning the QR code.
#[wasm_bindgen(getter)]
pub fn homeserver_url(&self) -> Option<String> {
if let qr_login::QrCodeModeData::Reciprocate { homeserver_url } = &self.inner.mode_data {
Some(homeserver_url.as_str().to_owned())
} else {
None
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we're adding new methods to the WASM bindings, please can we use #[wasm_bindgen(js_name)] to give them camelCase names, to be consistent with the rest of the bindings?


/// Get the mode of this [`QrCodeData`] instance.
#[wasm_bindgen(getter)]
pub fn mode(&self) -> QrCodeMode {
self.inner.mode().into()
}
}
71 changes: 70 additions & 1 deletion src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

use std::sync::Arc;

use matrix_sdk_crypto::store::{DynCryptoStore, IntoCryptoStore, MemoryStore};
use matrix_sdk_crypto::{
store::{DynCryptoStore, IntoCryptoStore, MemoryStore},
types::BackupSecrets,
};
use wasm_bindgen::prelude::*;

use crate::{
Expand Down Expand Up @@ -170,3 +173,69 @@ impl RoomKeyInfo {
self.inner.session_id.clone()
}
}

/// Struct containing the bundle of secrets to fully activate a new device for
/// end-to-end encryption.
#[derive(Debug)]
#[wasm_bindgen]
pub struct SecretsBundle {
pub(super) inner: matrix_sdk_crypto::types::SecretsBundle,
}

/// The backup-specific parts of a secrets bundle.
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct BackupSecretsBundle {
/// The backup decryption key, encoded as unpadded base64.
pub key: String,
/// The backup version which this backup decryption key is used with.
pub backup_version: String,
}

#[wasm_bindgen]
impl SecretsBundle {
/// The seed of the master key encoded as unpadded base64.
#[wasm_bindgen(getter, js_name = "masterKey")]
pub fn master_key(&self) -> String {
self.inner.cross_signing.master_key.clone()
}

/// The seed of the self signing key encoded as unpadded base64.
#[wasm_bindgen(getter, js_name = "selfSigningKey")]
pub fn self_signing_key(&self) -> String {
self.inner.cross_signing.self_signing_key.clone()
}

/// The seed of the user signing key encoded as unpadded base64.
#[wasm_bindgen(getter, js_name = "userSigningKey")]
pub fn user_signing_key(&self) -> String {
self.inner.cross_signing.user_signing_key.clone()
}

/// The bundle of the backup decryption key and backup version if any.
#[wasm_bindgen(getter, js_name = "backupBundle")]
pub fn backup_bundle(&self) -> Option<BackupSecretsBundle> {
if let Some(BackupSecrets::MegolmBackupV1Curve25519AesSha2(backup)) = &self.inner.backup {
Some(BackupSecretsBundle {
key: backup.key.to_base64(),
backup_version: backup.backup_version.clone(),
})
} else {
None
}
}

/// Serialize the [`SecretsBundle`] to a JSON object.
pub fn to_json(&self) -> Result<JsValue, JsError> {
Ok(serde_wasm_bindgen::to_value(&self.inner)?)
}

/// Deserialize the [`SecretsBundle`] from a JSON object.
pub fn from_json(json: JsValue) -> Result<SecretsBundle, JsError> {
let bundle = serde_wasm_bindgen::from_value(json)?;

Ok(Self { inner: bundle })
}
}

impl_from_to_inner!(matrix_sdk_crypto::types::SecretsBundle => SecretsBundle);
Loading
Loading