From 2e565475b0797881bd0e29a3fde0c85250ee3082 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 25 Jan 2024 16:36:37 +0000 Subject: [PATCH 1/2] Update matrix-sdk-crypto --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca50cd397..88248ab2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -863,7 +863,7 @@ dependencies = [ [[package]] name = "matrix-sdk-common" version = "0.7.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#eec52d7977c6122879a2d25f6ea81d76ea754280" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#4d2c3e33fcd00ea014a0bf433282ed76934ab452" dependencies = [ "async-trait", "futures-core", @@ -885,7 +885,7 @@ dependencies = [ [[package]] name = "matrix-sdk-crypto" version = "0.7.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#eec52d7977c6122879a2d25f6ea81d76ea754280" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#4d2c3e33fcd00ea014a0bf433282ed76934ab452" dependencies = [ "aes", "as_variant", @@ -947,7 +947,7 @@ dependencies = [ [[package]] name = "matrix-sdk-indexeddb" version = "0.7.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#eec52d7977c6122879a2d25f6ea81d76ea754280" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#4d2c3e33fcd00ea014a0bf433282ed76934ab452" dependencies = [ "anyhow", "async-trait", @@ -972,7 +972,7 @@ dependencies = [ [[package]] name = "matrix-sdk-qrcode" version = "0.7.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#eec52d7977c6122879a2d25f6ea81d76ea754280" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#4d2c3e33fcd00ea014a0bf433282ed76934ab452" dependencies = [ "byteorder", "qrcode", @@ -984,7 +984,7 @@ dependencies = [ [[package]] name = "matrix-sdk-store-encryption" version = "0.7.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#eec52d7977c6122879a2d25f6ea81d76ea754280" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#4d2c3e33fcd00ea014a0bf433282ed76934ab452" dependencies = [ "blake3", "chacha20poly1305", From 4bd82c35f7cdc810edc875a6f2114ac39ae4d2eb Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 17 Jan 2024 15:09:38 +0000 Subject: [PATCH 2/2] Support for saving and loading room settings Part of https://github.com/element-hq/element-web/issues/26108 --- CHANGELOG.md | 3 ++ src/machine.rs | 38 +++++++++++++++++++- src/types.rs | 81 ++++++++++++++++++++++++++++++++++++++++- tests/machine.test.ts | 83 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db93d1a65..8df21218f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) + # matrix-sdk-crypto-wasm v4.0.1 - `PickledInboundGroupSession.sender_signing_key` is now optional. diff --git a/src/machine.rs b/src/machine.rs index a38c87c17..53aa7e651 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -32,7 +32,7 @@ use crate::{ store, store::{RoomKeyInfo, StoreHandle}, sync_events, - types::{self, RoomKeyImportResult, SignatureVerification}, + types::{self, RoomKeyImportResult, RoomSettings, SignatureVerification}, verification, vodozemac, }; @@ -1379,6 +1379,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` + #[wasm_bindgen(js_name = "getRoomSettings")] + pub async fn get_room_settings( + &self, + room_id: &identifiers::RoomId, + ) -> Result { + 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. diff --git a/src/types.rs b/src/types.rs index 6ce6b116e..9117b9fc0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -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; @@ -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, @@ -283,3 +287,78 @@ impl From 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, + + /// 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, +} + +#[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 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), + } + } +} diff --git a/tests/machine.test.ts b/tests/machine.test.ts index e9f4390e9..11811a438 100644 --- a/tests/machine.test.ts +++ b/tests/machine.test.ts @@ -6,6 +6,7 @@ import { DeviceId, DeviceKeyId, DeviceLists, + EncryptionAlgorithm, EncryptionSettings, EventId, getVersions, @@ -21,6 +22,7 @@ import { RequestType, RoomId, RoomMessageRequest, + RoomSettings, ShieldColor, SignatureState, SignatureUploadRequest, @@ -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(); @@ -1388,4 +1397,78 @@ describe(OlmMachine.name, () => { expect(toDeviceRequests).toHaveLength(0); }); }); + + 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); + }); + }); });