Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to WebAssembly-powered Olm #743

Merged
merged 12 commits into from
Oct 25, 2018
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,13 +319,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.

Expand Down
15 changes: 9 additions & 6 deletions spec/unit/crypto.spec.js
Original file line number Diff line number Diff line change
@@ -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';
const Olm = global.Olm;

describe("Crypto", function() {
if (!sdk.CRYPTO_ENABLED) {
return;
}

beforeEach(function(done) {
Olm.init().then(done);
});

it("Crypto exposes the correct olm library version", function() {
expect(Crypto.getOlmVersion()[0]).toEqual(2);
expect(Crypto.getOlmVersion()[0]).toEqual(3);
});
});
17 changes: 7 additions & 10 deletions spec/unit/crypto/algorithms/megolm.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,16 @@ 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'];

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');
Expand Down Expand Up @@ -69,7 +65,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();

Expand Down Expand Up @@ -98,7 +95,7 @@ describe("MegolmDecryption", function() {
},
};

return event.attemptDecryption(mockCrypto).then(() => {
await event.attemptDecryption(mockCrypto).then(() => {
megolmDecryption.onRoomKeyEvent(event);
});
});
Expand Down
35 changes: 16 additions & 19 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,16 @@ const ContentHelpers = require("./content-helpers");
import ReEmitter from './ReEmitter';
import RoomList from './crypto/RoomList';

import Crypto from './crypto';
import { isCryptoAvailable } from './crypto';

// Disable warnings for now: we use deprecated bluebird functions
// and need to migrate, but they spam the console with warnings.
Promise.config({warnings: false});


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);
}
const CRYPTO_ENABLED = isCryptoAvailable();

/**
* Construct a Matrix Client. Only directly construct this if you want to use
Expand Down Expand Up @@ -133,6 +129,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();
Expand Down Expand Up @@ -185,10 +183,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.
Expand Down Expand Up @@ -378,6 +372,13 @@ 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?`,
);
}

if (this._crypto) {
console.warn("Attempt to re-initialise e2e encryption on MatrixClient");
return;
Expand All @@ -395,13 +396,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(
Expand Down Expand Up @@ -433,6 +427,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);
Expand Down
33 changes: 11 additions & 22 deletions src/crypto/OlmDevice.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,6 @@ limitations under the License.
import logger from '../logger';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';

/**
* olm.js wrapper
*
* @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
// reasonable approximation to the biggest plaintext we can encrypt.
const MAX_PLAINTEXT_LENGTH = 65536 * 3 / 4;
Expand Down Expand Up @@ -128,7 +117,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,
Expand Down Expand Up @@ -162,7 +151,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() {
Expand Down Expand Up @@ -269,7 +258,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);
Expand Down Expand Up @@ -322,7 +311,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);
Expand Down Expand Up @@ -355,7 +344,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 {
Expand Down Expand Up @@ -467,7 +456,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();
Expand Down Expand Up @@ -511,7 +500,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,
Expand Down Expand Up @@ -729,7 +718,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);
Expand All @@ -745,7 +734,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);
Expand Down Expand Up @@ -817,7 +806,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);
Expand Down Expand Up @@ -898,7 +887,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);
Expand Down
11 changes: 7 additions & 4 deletions src/crypto/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ const DeviceList = require('./DeviceList').default;
import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';

export function isCryptoAvailable() {
return Boolean(global.Olm);
}

/**
* Cryptography bits
*
Expand All @@ -63,7 +67,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;
Expand Down Expand Up @@ -125,6 +129,8 @@ utils.inherits(Crypto, EventEmitter);
* Returns a promise which resolves once the crypto module is ready for use.
*/
Crypto.prototype.init = async function() {
await global.Olm.init();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we wrapped this in an if (global.Olm.init), would everything still work with older versions of Olm?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm - probably. Not sure whether or not its worth trying.


const sessionStoreHasAccount = Boolean(this._sessionStore.getEndToEndAccount());
let cryptoStoreHasAccount;
await this._cryptoStore.doTxn(
Expand Down Expand Up @@ -1519,6 +1525,3 @@ class IncomingRoomKeyRequestCancellation {
* @event module:client~MatrixClient#"crypto.warning"
* @param {string} type One of the strings listed above
*/

/** */
module.exports = Crypto;
2 changes: 1 addition & 1 deletion travis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down