Skip to content
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

Support for saving and loading room settings #95

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

- Add new methods `OlmMachine.{get,set}RoomSettings`.
([#95](https://github.com/matrix-org/matrix-rust-sdk-crypto-wasm/pull/95))

- Add `OlmMachine.registerDevicesUpdatedCallback` to notify when devices have
been updated.
([#88](https://github.com/matrix-org/matrix-rust-sdk-crypto-wasm/pull/88))
Expand Down
10 changes: 5 additions & 5 deletions Cargo.lock

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

38 changes: 37 additions & 1 deletion src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::{
store,
store::{RoomKeyInfo, StoreHandle},
sync_events,
types::{self, RoomKeyImportResult, SignatureVerification},
types::{self, RoomKeyImportResult, RoomSettings, SignatureVerification},
verification, vodozemac,
};

Expand Down Expand Up @@ -1403,6 +1403,42 @@ impl OlmMachine {
})
}

/// Get the stored room settings, such as the encryption algorithm or
/// whether to encrypt only for trusted devices.
///
/// These settings can be modified via {@link #setRoomSettings}.
///
/// # Returns
///
/// `Promise<RoomSettings|undefined>`
#[wasm_bindgen(js_name = "getRoomSettings")]
pub async fn get_room_settings(
&self,
room_id: &identifiers::RoomId,
) -> Result<JsValue, JsError> {
let result = self.inner.room_settings(&room_id.inner).await?;
Ok(result.map(|settings| RoomSettings::from(settings)).into())
}

/// Store encryption settings for the given room.
///
/// This method checks if the new settings are "safe" -- ie, that they do
/// not represent a downgrade in encryption security from any previous
/// settings. Attempts to downgrade security will result in an error.
Copy link
Member

Choose a reason for hiding this comment

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

I don't see these checks in this code. Are they in the code we call into?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup.

///
/// If the settings are valid, they will be persisted to the crypto store.
/// These settings are not used directly by this library, but the saved
/// settings can be retrieved via {@link #getRoomSettings}.
#[wasm_bindgen(js_name = "setRoomSettings")]
pub async fn set_room_settings(
&self,
room_id: &identifiers::RoomId,
room_settings: &RoomSettings,
) -> Result<(), JsError> {
self.inner.set_room_settings(&room_id.inner, &room_settings.into()).await?;
Ok(())
}

/// Shut down the `OlmMachine`.
///
/// The `OlmMachine` cannot be used after this method has been called.
Expand Down
81 changes: 80 additions & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Extra types, like `Signatures`.

use std::collections::{BTreeMap, BTreeSet};
use std::{
collections::{BTreeMap, BTreeSet},
time::Duration,
};

use js_sys::{Array, JsString, Map, Set};
use matrix_sdk_common::ruma::OwnedRoomId;
Expand All @@ -10,6 +13,7 @@ use matrix_sdk_crypto::backups::{
use wasm_bindgen::prelude::*;

use crate::{
encryption::EncryptionAlgorithm,
identifiers::{DeviceKeyId, UserId},
impl_from_to_inner,
vodozemac::Ed25519Signature,
Expand Down Expand Up @@ -283,3 +287,78 @@ impl From<matrix_sdk_crypto::RoomKeyImportResult> for RoomKeyImportResult {
}
}
}

/// Room encryption settings which are modified by state events or user options
#[derive(Clone, Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct RoomSettings {
/// The encryption algorithm that should be used in the room.
///
/// Should be one of the members of {@link EncryptionAlgorithm}.
pub algorithm: EncryptionAlgorithm,

/// Whether untrusted devices should receive room keys. If this is `false`,
/// they will be excluded from the conversation.
#[wasm_bindgen(js_name = "onlyAllowTrustedDevices")]
pub only_allow_trusted_devices: bool,

/// The maximum time, in milliseconds, that an encryption session should be
/// used for, before it is rotated.
#[wasm_bindgen(js_name = "sessionRotationPeriodMs")]
pub session_rotation_period_ms: Option<f64>,

/// The maximum number of messages an encryption session should be used for,
/// before it is rotated.
#[wasm_bindgen(js_name = "sessionRotationPeriodMessages")]
pub session_rotation_period_messages: Option<f64>,
}

#[wasm_bindgen]
impl RoomSettings {
/// Create a new `RoomSettings` with default values.
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self::default()
}
}

impl Default for RoomSettings {
fn default() -> Self {
Self {
algorithm: EncryptionAlgorithm::MegolmV1AesSha2,
only_allow_trusted_devices: false,
session_rotation_period_ms: None,
session_rotation_period_messages: None,
}
}
}

impl From<matrix_sdk_crypto::store::RoomSettings> for RoomSettings {
fn from(value: matrix_sdk_crypto::store::RoomSettings) -> Self {
Self {
algorithm: value.algorithm.into(),
only_allow_trusted_devices: value.only_allow_trusted_devices,
session_rotation_period_ms: value
.session_rotation_period
.map(|duration| duration.as_millis() as f64),
session_rotation_period_messages: value
.session_rotation_period_messages
.map(|count| count as f64),
}
}
}

impl From<&RoomSettings> for matrix_sdk_crypto::store::RoomSettings {
fn from(value: &RoomSettings) -> Self {
Self {
algorithm: value.algorithm.clone().into(),
only_allow_trusted_devices: value.only_allow_trusted_devices,
session_rotation_period: value
.session_rotation_period_ms
.map(|millis| Duration::from_millis(millis as u64)),
session_rotation_period_messages: value
.session_rotation_period_messages
.map(|count| count as usize),
}
}
}
83 changes: 83 additions & 0 deletions tests/machine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
DeviceId,
DeviceKeyId,
DeviceLists,
EncryptionAlgorithm,
EncryptionSettings,
EventId,
getVersions,
Expand All @@ -21,6 +22,7 @@ import {
RequestType,
RoomId,
RoomMessageRequest,
RoomSettings,
ShieldColor,
SignatureState,
SignatureUploadRequest,
Expand All @@ -42,6 +44,13 @@ type AnyOutgoingRequest =
| RoomMessageRequest
| KeysBackupRequest;

afterEach(() => {
// reset fake-indexeddb after each test, to make sure we don't leak data
// cf https://github.com/dumbmatter/fakeIndexedDB#wipingresetting-the-indexeddb-for-a-fresh-state
// eslint-disable-next-line no-global-assign
indexedDB = new IDBFactory();
});

describe("Versions", () => {
test("can find out the crate versions", async () => {
const versions = getVersions();
Expand Down Expand Up @@ -1421,4 +1430,78 @@ describe(OlmMachine.name, () => {
}
expect(callback).toHaveBeenCalledWith(["@alice:example.org"]);
});

describe.each(["passphrase", undefined])("Room settings (store passphrase '%s')", (storePassphrase) => {
let m: OlmMachine;

beforeEach(async () => {
m = await OlmMachine.initialize(user, device, "store_prefix", storePassphrase);
});

test("Should return undefined for an unknown room", async () => {
await m.setRoomSettings(new RoomId("!test:room"), new RoomSettings());
const settings = await m.getRoomSettings(new RoomId("!test1:room"));
expect(settings).toBe(undefined);
});

test("Should store and return room settings", async () => {
const settings = new RoomSettings();
settings.algorithm = EncryptionAlgorithm.MegolmV1AesSha2;
settings.onlyAllowTrustedDevices = true;
settings.sessionRotationPeriodMs = 10000;
settings.sessionRotationPeriodMessages = 1234;

await m.setRoomSettings(new RoomId("!test:room"), settings);

const loadedSettings = await m.getRoomSettings(new RoomId("!test:room"));
expect(loadedSettings.algorithm).toEqual(EncryptionAlgorithm.MegolmV1AesSha2);
expect(loadedSettings.onlyAllowTrustedDevices).toBe(true);
expect(loadedSettings.sessionRotationPeriodMs).toEqual(10000);
expect(loadedSettings.sessionRotationPeriodMessages).toEqual(1234);
});

test("Should reject unsupported algorithms", async () => {
const settings = new RoomSettings();
settings.algorithm = EncryptionAlgorithm.OlmV1Curve25519AesSha2;
await expect(m.setRoomSettings(new RoomId("!test:room"), settings)).rejects.toThrow(
/the new settings are invalid/,
);
});

test("Should reject downgrade attacks", async () => {
const settings = new RoomSettings();
settings.algorithm = EncryptionAlgorithm.MegolmV1AesSha2;
settings.onlyAllowTrustedDevices = true;
settings.sessionRotationPeriodMs = 100;
settings.sessionRotationPeriodMessages = 10;
await m.setRoomSettings(new RoomId("!test:room"), settings);

// Try to increase the rotation period
settings.sessionRotationPeriodMs = 1000;
await expect(m.setRoomSettings(new RoomId("!test:room"), settings)).rejects.toThrow(/downgrade/);

// Check the old settings persist
const loadedSettings = await m.getRoomSettings(new RoomId("!test:room"));
expect(loadedSettings.algorithm).toEqual(EncryptionAlgorithm.MegolmV1AesSha2);
expect(loadedSettings.onlyAllowTrustedDevices).toBe(true);
expect(loadedSettings.sessionRotationPeriodMs).toEqual(100);
expect(loadedSettings.sessionRotationPeriodMessages).toEqual(10);
});

test("Should ignore no-op changes", async () => {
const settings = new RoomSettings();
settings.algorithm = EncryptionAlgorithm.MegolmV1AesSha2;
settings.onlyAllowTrustedDevices = true;
settings.sessionRotationPeriodMs = 100;
settings.sessionRotationPeriodMessages = 10;
await m.setRoomSettings(new RoomId("!test:room"), settings);

const settings2 = new RoomSettings();
settings2.algorithm = EncryptionAlgorithm.MegolmV1AesSha2;
settings2.onlyAllowTrustedDevices = true;
settings2.sessionRotationPeriodMs = 100;
settings2.sessionRotationPeriodMessages = 10;
await m.setRoomSettings(new RoomId("!test:room"), settings2);
});
});
});
Loading