From c556ca40b159e141b90e542cd8f82329fdce6043 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 25 Sep 2018 17:49:54 +0100 Subject: [PATCH 1/3] Support Olm with WebAssembly wasm Olm has a new interface: it now has an init method that needs to be called and the promise it returns waited on before the Olm module is used. Support that, and allow Crypto etc to be imported whether Olm is enabled or not. Change whether olm is enabled to be async since now it will be unavailable if the async module init fails. Don't call getOlmVersion() until the Olm.init() is done. --- spec/unit/crypto.spec.js | 13 ++++---- spec/unit/crypto/algorithms/megolm.spec.js | 15 ++++------ src/client.js | 35 ++++++++++------------ src/crypto/OlmDevice.js | 3 -- src/crypto/index.js | 15 +++++++--- 5 files changed, 40 insertions(+), 41 deletions(-) diff --git a/spec/unit/crypto.spec.js b/spec/unit/crypto.spec.js index 4d949e05e..734941cb5 100644 --- a/spec/unit/crypto.spec.js +++ b/spec/unit/crypto.spec.js @@ -1,20 +1,23 @@ "use strict"; import 'source-map-support/register'; +import Crypto from '../../lib/crypto'; +import expect from 'expect'; const sdk = require("../.."); -let Crypto; -if (sdk.CRYPTO_ENABLED) { - Crypto = require("../../lib/crypto"); -} -import expect from 'expect'; describe("Crypto", function() { if (!sdk.CRYPTO_ENABLED) { return; } + + beforeEach(function(done) { + Olm.init().then(done); + }); + it("Crypto exposes the correct olm library version", function() { + console.log(Crypto); expect(Crypto.getOlmVersion()[0]).toEqual(2); }); }); diff --git a/spec/unit/crypto/algorithms/megolm.spec.js b/spec/unit/crypto/algorithms/megolm.spec.js index cf8e58f2e..db28e3a51 100644 --- a/spec/unit/crypto/algorithms/megolm.spec.js +++ b/spec/unit/crypto/algorithms/megolm.spec.js @@ -13,14 +13,8 @@ import WebStorageSessionStore from '../../../../lib/store/session/webstorage'; import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js'; import MockStorageApi from '../../../MockStorageApi'; import testUtils from '../../../test-utils'; - -// Crypto and OlmDevice won't import unless we have global.Olm -let OlmDevice; -let Crypto; -if (global.Olm) { - OlmDevice = require('../../../../lib/crypto/OlmDevice'); - Crypto = require('../../../../lib/crypto'); -} +import OlmDevice from '../../../../lib/crypto/OlmDevice'; +import Crypto from '../../../../lib/crypto'; const MatrixEvent = sdk.MatrixEvent; const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; @@ -69,7 +63,8 @@ describe("MegolmDecryption", function() { describe('receives some keys:', function() { let groupSession; - beforeEach(function() { + beforeEach(async function() { + await Olm.init(); groupSession = new global.Olm.OutboundGroupSession(); groupSession.create(); @@ -98,7 +93,7 @@ describe("MegolmDecryption", function() { }, }; - return event.attemptDecryption(mockCrypto).then(() => { + await event.attemptDecryption(mockCrypto).then(() => { megolmDecryption.onRoomKeyEvent(event); }); }); diff --git a/src/client.js b/src/client.js index 8f0ca893d..8ca115a38 100644 --- a/src/client.js +++ b/src/client.js @@ -45,6 +45,8 @@ const ContentHelpers = require("./content-helpers"); import ReEmitter from './ReEmitter'; import RoomList from './crypto/RoomList'; +import Crypto from './crypto'; +import { isCryptoAvailable } from './crypto'; const LAZY_LOADING_MESSAGES_FILTER = { lazy_load_members: true, @@ -58,14 +60,7 @@ const LAZY_LOADING_SYNC_FILTER = { const SCROLLBACK_DELAY_MS = 3000; -let CRYPTO_ENABLED = false; - -try { - var Crypto = require("./crypto"); - CRYPTO_ENABLED = true; -} catch (e) { - console.warn("Unable to load crypto module: crypto will be disabled: " + e); -} +let CRYPTO_ENABLED = isCryptoAvailable(); /** * Construct a Matrix Client. Only directly construct this if you want to use @@ -140,6 +135,8 @@ function MatrixClient(opts) { MatrixBaseApis.call(this, opts); + this.olmVersion = null; // Populated after initCrypto is done + this.reEmitter = new ReEmitter(this); this.store = opts.store || new StubStore(); @@ -192,10 +189,6 @@ function MatrixClient(opts) { this._forceTURN = opts.forceTURN || false; - if (CRYPTO_ENABLED) { - this.olmVersion = Crypto.getOlmVersion(); - } - // List of which rooms have encryption enabled: separate from crypto because // we still want to know which rooms are encrypted even if crypto is disabled: // we don't want to start sending unencrypted events to them. @@ -385,6 +378,14 @@ MatrixClient.prototype.setNotifTimelineSet = function(notifTimelineSet) { * successfully initialised. */ MatrixClient.prototype.initCrypto = async function() { + if (!isCryptoAvailable()) { + throw new Error( + `End-to-end encryption not supported in this js-sdk build: did ` + + `you remember to load the olm library?`, + ); + return; + } + if (this._crypto) { console.warn("Attempt to re-initialise e2e encryption on MatrixClient"); return; @@ -402,13 +403,6 @@ MatrixClient.prototype.initCrypto = async function() { // initialise the list of encrypted rooms (whether or not crypto is enabled) await this._roomList.init(); - if (!CRYPTO_ENABLED) { - throw new Error( - `End-to-end encryption not supported in this js-sdk build: did ` + - `you remember to load the olm library?`, - ); - } - const userId = this.getUserId(); if (userId === null) { throw new Error( @@ -440,6 +434,9 @@ MatrixClient.prototype.initCrypto = async function() { await crypto.init(); + this.olmVersion = Crypto.getOlmVersion(); + + // if crypto initialisation was successful, tell it to attach its event // handlers. crypto.registerEventHandlers(this); diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index cda14779c..b8c70cd0c 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -23,9 +23,6 @@ import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; * @module crypto/OlmDevice */ const Olm = global.Olm; -if (!Olm) { - throw new Error("global.Olm is not defined"); -} // The maximum size of an event is 65K, and we base64 the content, so this is a diff --git a/src/crypto/index.js b/src/crypto/index.js index f00477d4b..0fe33663f 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -36,6 +36,12 @@ const DeviceList = require('./DeviceList').default; import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager'; import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; +const Olm = global.Olm; + +export function isCryptoAvailable() { + return Boolean(Olm); +} + /** * Cryptography bits * @@ -62,7 +68,7 @@ import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; * * @param {RoomList} roomList An initialised RoomList object */ -function Crypto(baseApis, sessionStore, userId, deviceId, +export default function Crypto(baseApis, sessionStore, userId, deviceId, clientStore, cryptoStore, roomList) { this._baseApis = baseApis; this._sessionStore = sessionStore; @@ -124,6 +130,10 @@ utils.inherits(Crypto, EventEmitter); * Returns a promise which resolves once the crypto module is ready for use. */ Crypto.prototype.init = async function() { + // Olm is just an object with a .then, not a fully-fledged promise, so + // pass it into bluebird to make it a proper promise. + await Olm.init(); + const sessionStoreHasAccount = Boolean(this._sessionStore.getEndToEndAccount()); let cryptoStoreHasAccount; await this._cryptoStore.doTxn( @@ -1518,6 +1528,3 @@ class IncomingRoomKeyRequestCancellation { * @event module:client~MatrixClient#"crypto.warning" * @param {string} type One of the strings listed above */ - -/** */ -module.exports = Crypto; From 63cc3fd890b9927c489d65e50895a0ba74196ca4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 25 Sep 2018 18:14:11 +0100 Subject: [PATCH 2/3] lint --- spec/unit/crypto.spec.js | 1 + spec/unit/crypto/algorithms/megolm.spec.js | 2 ++ src/client.js | 3 +-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/unit/crypto.spec.js b/spec/unit/crypto.spec.js index 734941cb5..1b28ad683 100644 --- a/spec/unit/crypto.spec.js +++ b/spec/unit/crypto.spec.js @@ -6,6 +6,7 @@ import expect from 'expect'; const sdk = require("../.."); +const Olm = global.Olm; describe("Crypto", function() { if (!sdk.CRYPTO_ENABLED) { diff --git a/spec/unit/crypto/algorithms/megolm.spec.js b/spec/unit/crypto/algorithms/megolm.spec.js index db28e3a51..6c777859e 100644 --- a/spec/unit/crypto/algorithms/megolm.spec.js +++ b/spec/unit/crypto/algorithms/megolm.spec.js @@ -21,6 +21,8 @@ const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; const ROOM_ID = '!ROOM:ID'; +const Olm = global.Olm; + describe("MegolmDecryption", function() { if (!global.Olm) { console.warn('Not running megolm unit tests: libolm not present'); diff --git a/src/client.js b/src/client.js index 8ca115a38..1a05998eb 100644 --- a/src/client.js +++ b/src/client.js @@ -60,7 +60,7 @@ const LAZY_LOADING_SYNC_FILTER = { const SCROLLBACK_DELAY_MS = 3000; -let CRYPTO_ENABLED = isCryptoAvailable(); +const CRYPTO_ENABLED = isCryptoAvailable(); /** * Construct a Matrix Client. Only directly construct this if you want to use @@ -383,7 +383,6 @@ MatrixClient.prototype.initCrypto = async function() { `End-to-end encryption not supported in this js-sdk build: did ` + `you remember to load the olm library?`, ); - return; } if (this._crypto) { From 33ad65a105d379b6208a950679e3c5bb80525698 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 26 Sep 2018 16:39:22 +0100 Subject: [PATCH 3/3] Don't assume Olm will be available from start By doing `Olm = global.Olm` on script load, we require that Olm is available right from the start, which isn't really necessary. As long as it appears some time before we actually want to use it, this is fine (we can probably assume it's not going to go away again..?) This means Riot doesn't need to faff about making sure olm is loaded before starting anything else. --- src/crypto/OlmDevice.js | 30 +++++++++++------------------- src/crypto/index.js | 6 ++---- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index b8c70cd0c..043905c09 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -17,14 +17,6 @@ limitations under the License. import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; -/** - * olm.js wrapper - * - * @module crypto/OlmDevice - */ -const Olm = global.Olm; - - // The maximum size of an event is 65K, and we base64 the content, so this is a // reasonable approximation to the biggest plaintext we can encrypt. const MAX_PLAINTEXT_LENGTH = 65536 * 3 / 4; @@ -124,7 +116,7 @@ OlmDevice.prototype.init = async function() { await this._migrateFromSessionStore(); let e2eKeys; - const account = new Olm.Account(); + const account = new global.Olm.Account(); try { await _initialiseAccount( this._sessionStore, this._cryptoStore, this._pickleKey, account, @@ -158,7 +150,7 @@ async function _initialiseAccount(sessionStore, cryptoStore, pickleKey, account) * @return {array} The version of Olm. */ OlmDevice.getOlmVersion = function() { - return Olm.get_library_version(); + return global.Olm.get_library_version(); }; OlmDevice.prototype._migrateFromSessionStore = async function() { @@ -265,7 +257,7 @@ OlmDevice.prototype._migrateFromSessionStore = async function() { */ OlmDevice.prototype._getAccount = function(txn, func) { this._cryptoStore.getAccount(txn, (pickledAccount) => { - const account = new Olm.Account(); + const account = new global.Olm.Account(); try { account.unpickle(this._pickleKey, pickledAccount); func(account); @@ -318,7 +310,7 @@ OlmDevice.prototype._getSession = function(deviceKey, sessionId, txn, func) { * @private */ OlmDevice.prototype._unpickleSession = function(pickledSession, func) { - const session = new Olm.Session(); + const session = new global.Olm.Session(); try { session.unpickle(this._pickleKey, pickledSession); func(session); @@ -351,7 +343,7 @@ OlmDevice.prototype._saveSession = function(deviceKey, session, txn) { * @private */ OlmDevice.prototype._getUtility = function(func) { - const utility = new Olm.Utility(); + const utility = new global.Olm.Utility(); try { return func(utility); } finally { @@ -463,7 +455,7 @@ OlmDevice.prototype.createOutboundSession = async function( ], (txn) => { this._getAccount(txn, (account) => { - const session = new Olm.Session(); + const session = new global.Olm.Session(); try { session.create_outbound(account, theirIdentityKey, theirOneTimeKey); newSessionId = session.session_id(); @@ -507,7 +499,7 @@ OlmDevice.prototype.createInboundSession = async function( ], (txn) => { this._getAccount(txn, (account) => { - const session = new Olm.Session(); + const session = new global.Olm.Session(); try { session.create_inbound_from( account, theirDeviceIdentityKey, ciphertext, @@ -725,7 +717,7 @@ OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) { throw new Error("Unknown outbound group session " + sessionId); } - const session = new Olm.OutboundGroupSession(); + const session = new global.Olm.OutboundGroupSession(); try { session.unpickle(this._pickleKey, pickled); return func(session); @@ -741,7 +733,7 @@ OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) { * @return {string} sessionId for the outbound session. */ OlmDevice.prototype.createOutboundGroupSession = function() { - const session = new Olm.OutboundGroupSession(); + const session = new global.Olm.OutboundGroupSession(); try { session.create(); this._saveOutboundGroupSession(session); @@ -813,7 +805,7 @@ OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) { * @return {*} result of func */ OlmDevice.prototype._unpickleInboundGroupSession = function(sessionData, func) { - const session = new Olm.InboundGroupSession(); + const session = new global.Olm.InboundGroupSession(); try { session.unpickle(this._pickleKey, sessionData.session); return func(session); @@ -894,7 +886,7 @@ OlmDevice.prototype.addInboundGroupSession = async function( } // new session. - const session = new Olm.InboundGroupSession(); + const session = new global.Olm.InboundGroupSession(); try { if (exportFormat) { session.import_session(sessionKey); diff --git a/src/crypto/index.js b/src/crypto/index.js index 0fe33663f..9ed3c7113 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -36,10 +36,8 @@ const DeviceList = require('./DeviceList').default; import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager'; import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; -const Olm = global.Olm; - export function isCryptoAvailable() { - return Boolean(Olm); + return Boolean(global.Olm); } /** @@ -132,7 +130,7 @@ utils.inherits(Crypto, EventEmitter); Crypto.prototype.init = async function() { // Olm is just an object with a .then, not a fully-fledged promise, so // pass it into bluebird to make it a proper promise. - await Olm.init(); + await global.Olm.init(); const sessionStoreHasAccount = Boolean(this._sessionStore.getEndToEndAccount()); let cryptoStoreHasAccount;