From c556ca40b159e141b90e542cd8f82329fdce6043 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 25 Sep 2018 17:49:54 +0100 Subject: [PATCH 1/8] 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 4d949e05eb1..734941cb5fb 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 cf8e58f2e0b..db28e3a5161 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 8f0ca893db2..8ca115a3891 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 cda14779c2b..b8c70cd0c66 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 f00477d4b50..0fe33663fae 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/8] 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 734941cb5fb..1b28ad683df 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 db28e3a5161..6c777859ebf 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 8ca115a3891..1a05998eb6d 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/8] 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 b8c70cd0c66..043905c098d 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 0fe33663fae..9ed3c7113a5 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; From 35d584c67bddf0b7337ea1438888de12b7a1e74f Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 4 Oct 2018 13:05:45 +0100 Subject: [PATCH 4/8] Remove outdated comment --- src/crypto/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/crypto/index.js b/src/crypto/index.js index 9ed3c7113a5..1e0e18d138c 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -128,8 +128,6 @@ 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 global.Olm.init(); const sessionStoreHasAccount = Boolean(this._sessionStore.getEndToEndAccount()); From b9e198c172906d287b8c0257e6db1b6ec617c754 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 15 Oct 2018 11:39:39 +0100 Subject: [PATCH 5/8] Oops: remove debug logging --- spec/unit/crypto.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/unit/crypto.spec.js b/spec/unit/crypto.spec.js index 1b28ad683df..d48879882a9 100644 --- a/spec/unit/crypto.spec.js +++ b/spec/unit/crypto.spec.js @@ -18,7 +18,6 @@ describe("Crypto", function() { }); it("Crypto exposes the correct olm library version", function() { - console.log(Crypto); expect(Crypto.getOlmVersion()[0]).toEqual(2); }); }); From 84b91d4575d4ca8bdd545e5235c335c9e227c903 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Oct 2018 16:58:48 +0100 Subject: [PATCH 6/8] Update to Olm 3 --- README.md | 4 ++-- travis.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ec11da0a34d..6ba36d9440a 100644 --- a/README.md +++ b/README.md @@ -267,13 +267,13 @@ To provide the Olm library in a browser application: To provide the Olm library in a node.js application: - * ``npm install https://matrix.org/packages/npm/olm/olm-2.2.2.tgz`` + * ``npm install https://matrix.org/packages/npm/olm/olm-3.0.0.tgz`` (replace the URL with the latest version you want to use from https://matrix.org/packages/npm/olm/) * ``global.Olm = require('olm');`` *before* loading ``matrix-js-sdk``. If you want to package Olm as dependency for your node.js application, you -can use ``npm install https://matrix.org/packages/npm/olm/olm-2.2.2.tgz +can use ``npm install https://matrix.org/packages/npm/olm/olm-3.0.0.tgz --save-optional`` (if your application also works without e2e crypto enabled) or ``--save`` (if it doesn't) to do so. diff --git a/travis.sh b/travis.sh index 68d915defe4..ddf4a790ecd 100755 --- a/travis.sh +++ b/travis.sh @@ -5,7 +5,7 @@ set -ex npm run lint # install Olm so that we can run the crypto tests. -npm install https://matrix.org/packages/npm/olm/olm-2.2.2.tgz +npm install https://matrix.org/packages/npm/olm/olm-3.0.0.tgz npm run test From 3a316de9ef5ca23c7970953b40e1bd8e4ffb148a Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Oct 2018 17:40:58 +0100 Subject: [PATCH 7/8] Update to Olm 3 here too --- spec/unit/crypto.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/crypto.spec.js b/spec/unit/crypto.spec.js index d48879882a9..ee06ef369c0 100644 --- a/spec/unit/crypto.spec.js +++ b/spec/unit/crypto.spec.js @@ -18,6 +18,6 @@ describe("Crypto", function() { }); it("Crypto exposes the correct olm library version", function() { - expect(Crypto.getOlmVersion()[0]).toEqual(2); + expect(Crypto.getOlmVersion()[0]).toEqual(3); }); }); From d29ac088c0618007b7abb7146ccbf1409db5d6ac Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Oct 2018 18:55:04 +0100 Subject: [PATCH 8/8] retest