Skip to content

Commit

Permalink
Support for saving and loading room settings (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
richvdh authored Jan 25, 2024
1 parent 5638bbb commit c3b9a6a
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 7 deletions.
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.
///
/// 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);
});
});
});

0 comments on commit c3b9a6a

Please sign in to comment.