diff --git a/Cargo.lock b/Cargo.lock index 163fab785..78ef18cb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1931,8 +1931,7 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexed_db_futures" version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26ac735f676c52305becf53264b91cea9866a8de61ccbf464405b377b9cbca9" +source = "git+https://github.com/Hywan/rust-indexed-db?branch=feat-factory-nodejs#5dab67890cea0ab88b967031adc09179a537d77c" dependencies = [ "cfg-if", "js-sys", @@ -2365,6 +2364,7 @@ dependencies = [ "js-sys", "matrix-sdk-common", "matrix-sdk-crypto", + "matrix-sdk-indexeddb", "ruma", "serde_json", "tracing", @@ -2372,6 +2372,7 @@ dependencies = [ "vodozemac", "wasm-bindgen", "wasm-bindgen-futures", + "zeroize", ] [[package]] diff --git a/bindings/matrix-sdk-crypto-js/Cargo.toml b/bindings/matrix-sdk-crypto-js/Cargo.toml index fa86ac7d2..a75349b79 100644 --- a/bindings/matrix-sdk-crypto-js/Cargo.toml +++ b/bindings/matrix-sdk-crypto-js/Cargo.toml @@ -28,6 +28,7 @@ tracing = [] [dependencies] matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" } matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" } +matrix-sdk-indexeddb = { version = "0.1.0", path = "../../crates/matrix-sdk-indexeddb" } ruma = { git = "https://github.com/ruma/ruma", rev = "173eb15147c904d7dc0ee894de6114743926e33e", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } wasm-bindgen = "0.2.80" wasm-bindgen-futures = "0.4.30" @@ -38,6 +39,7 @@ http = "0.2.6" anyhow = "1.0.58" tracing = { version = "0.1.35", default-features = false, features = ["attributes"] } tracing-subscriber = { version = "0.3.14", default-features = false, features = ["registry", "std"] } +zeroize = "1.3.0" [dependencies.vodozemac] git = "https://github.com/matrix-org/vodozemac/" diff --git a/bindings/matrix-sdk-crypto-js/package.json b/bindings/matrix-sdk-crypto-js/package.json index 59cce9d60..8de6990e5 100644 --- a/bindings/matrix-sdk-crypto-js/package.json +++ b/bindings/matrix-sdk-crypto-js/package.json @@ -30,7 +30,8 @@ "jest": "^28.1.0", "typedoc": "^0.22.17", "cross-env": "^7.0.3", - "yargs-parser": "~21.0.1" + "yargs-parser": "~21.0.1", + "fake-indexeddb": "^4.0" }, "engines": { "node": ">= 10" diff --git a/bindings/matrix-sdk-crypto-js/src/machine.rs b/bindings/matrix-sdk-crypto-js/src/machine.rs index 33c19f6f7..cd7d29e7a 100644 --- a/bindings/matrix-sdk-crypto-js/src/machine.rs +++ b/bindings/matrix-sdk-crypto-js/src/machine.rs @@ -34,16 +34,74 @@ impl OlmMachine { /// `user_id` represents the unique ID of the user that owns this /// machine. `device_id` represents the unique ID of the device /// that owns this machine. + /// + /// `store_name` and `store_passphrase` are both optional, but + /// must be both set to have an effect. If they are both set, the + /// state of the machine will persist in a database named + /// `store_name` where its content is encrypted by the passphrase + /// given by `store_passphrase`. If they are not both set, the + /// created machine will keep the encryption keys only in memory, + /// and once the object is dropped, the keys will be lost. #[wasm_bindgen(constructor)] #[allow(clippy::new_ret_no_self)] - pub fn new(user_id: &identifiers::UserId, device_id: &identifiers::DeviceId) -> Promise { + pub fn new( + user_id: &identifiers::UserId, + device_id: &identifiers::DeviceId, + store_name: Option, + store_passphrase: Option, + ) -> Promise { let user_id = user_id.inner.clone(); let device_id = device_id.inner.clone(); future_to_promise(async move { + let store = match (store_name, store_passphrase) { + // We need this `#[cfg]` because `IndexeddbCryptoStore` + // implements `CryptoStore` only on `target_arch = + // "wasm32"`. Without that, we could have a compilation + // error when checking the entire workspace. In + // practise, it doesn't impact this crate because it's + // always compiled for `wasm32`. + #[cfg(target_arch = "wasm32")] + (Some(store_name), Some(mut store_passphrase)) => { + use std::sync::Arc; + use zeroize::Zeroize; + + let store = Some( + matrix_sdk_indexeddb::IndexeddbCryptoStore::open_with_passphrase( + &store_name, + &store_passphrase, + ) + .await + .map(Arc::new)?, + ); + + store_passphrase.zeroize(); + + store + } + + (Some(_), None) => return Err(anyhow::Error::msg("The `store_name` has been set, and so, it expects a `store_passphrase`, which is not set; please provide one")), + + (None, Some(_)) => return Err(anyhow::Error::msg("The `store_passphrase` has been set, but it has an effect only if `store_name` is set, which is not; please provide one")), + + _ => None, + }; + Ok(OlmMachine { - inner: matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref()) - .await, + inner: match store { + Some(store) => { + matrix_sdk_crypto::OlmMachine::with_store( + user_id.as_ref(), + device_id.as_ref(), + store, + ) + .await? + } + None => { + matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref()) + .await + } + }, }) }) } diff --git a/bindings/matrix-sdk-crypto-js/tests/machine.test.js b/bindings/matrix-sdk-crypto-js/tests/machine.test.js index c798aac53..d5c918622 100644 --- a/bindings/matrix-sdk-crypto-js/tests/machine.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/machine.test.js @@ -1,10 +1,69 @@ const { OlmMachine, UserId, DeviceId, DeviceKeyId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState, CrossSigningStatus, MaybeSignature } = require('../pkg/matrix_sdk_crypto_js'); +require('fake-indexeddb/auto'); describe(OlmMachine.name, () => { test('can be instantiated with the async initializer', async () => { expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine); }); + test('can be instantiated with a store', async () => { + // No databases. + expect(await indexedDB.databases()).toHaveLength(0); + + let store_name = 'hello'; + let store_passphrase = 'world'; + + // Creating a new Olm machine. + expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine); + + // Oh, there is 2 databases now, prefixed by `store_name`. + let databases = await indexedDB.databases(); + + expect(databases).toHaveLength(2); + expect(databases).toStrictEqual([ + { name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 }, + { name: `${store_name}::matrix-sdk-crypto`, version: 1 }, + ]); + + // Creating a new Olm machine, with the stored state. + expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine); + + // Same number of databases. + expect(await indexedDB.databases()).toHaveLength(2); + }); + + describe('cannot be instantiated with a store', () => { + test('store name is missing', async () => { + let store_name = null; + let store_passphrase = 'world'; + + let err = null; + + try { + await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase); + } catch (error) { + err = error; + } + + expect(err).toBeDefined(); + }); + + test('store passphrase is missing', async () => { + let store_name = 'hello'; + let store_passphrase = null; + + let err = null; + + try { + await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase); + } catch (error) { + err = error; + } + + expect(err).toBeDefined(); + }); + }); + const user = new UserId('@alice:example.org'); const device = new DeviceId('foobar'); const room = new RoomId('!baz:matrix.org'); diff --git a/crates/matrix-sdk-indexeddb/Cargo.toml b/crates/matrix-sdk-indexeddb/Cargo.toml index 259af8c5c..79f33a924 100644 --- a/crates/matrix-sdk-indexeddb/Cargo.toml +++ b/crates/matrix-sdk-indexeddb/Cargo.toml @@ -26,7 +26,7 @@ base64 = "0.13.0" dashmap = { version = "5.2.0", optional = true } derive_builder = "0.11.2" futures-util = { version = " 0.3.21", default-features = false, features = ["alloc"], optional = true } -indexed_db_futures = "0.2.3" +indexed_db_futures = { git = "https://github.com/Hywan/rust-indexed-db", branch = "feat-factory-nodejs" } js-sys = { version = "0.3.58" } matrix-sdk-base = { version = "0.5.0", path = "../matrix-sdk-base" } matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto", optional = true }