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

Make lots of OlmDevice asynchronous #524

Merged
merged 18 commits into from
Aug 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion spec/integ/matrix-client-crypto.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ function expectAliClaimKeys() {
// it can take a while to process the key query, so give it some extra
// time, and make sure the claim actually happens rather than ploughing on
// confusingly.
return aliTestClient.httpBackend.flush("/keys/claim", 1).then((r) => {
return aliTestClient.httpBackend.flush("/keys/claim", 1, 500).then((r) => {
expect(r).toEqual(1, "Ali did not claim Bob's keys");
});
});
Expand Down
12 changes: 12 additions & 0 deletions spec/integ/megolm-integ.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,18 @@ describe("megolm", function() {
}).then(function() {
const room = aliceTestClient.client.getRoom(ROOM_ID);
const event = room.getLiveTimeline().getEvents()[0];

if (event.getContent().msgtype != 'm.bad.encrypted') {
return event;
}

return new Promise((resolve, reject) => {
event.once('Event.decrypted', (ev) => {
console.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
});
});
}).then((event) => {
expect(event.getContent().body).toEqual('42');
});
});
Expand Down
16 changes: 10 additions & 6 deletions spec/unit/crypto/algorithms/megolm.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,16 @@ describe("MegolmDecryption", function() {
const deviceInfo = {};
mockCrypto.getStoredDevice.andReturn(deviceInfo);

const awaitEnsureSessions = new Promise((res, rej) => {
mockOlmLib.ensureOlmSessionsForDevices.andCall(() => {
mockOlmLib.ensureOlmSessionsForDevices.andReturn(
Promise.resolve({'@alice:foo': {'alidevice': {
sessionId: 'alisession',
}}}),
);

const awaitEncryptForDevice = new Promise((res, rej) => {
mockOlmLib.encryptMessageForDevice.andCall(() => {
res();
return Promise.resolve({'@alice:foo': {'alidevice': {
sessionId: 'alisession',
}}});
return Promise.resolve();
});
});

Expand All @@ -156,7 +160,7 @@ describe("MegolmDecryption", function() {
megolmDecryption.shareKeysWithDevice(keyRequest);

// it's asynchronous, so we have to wait a bit
return awaitEnsureSessions;
return awaitEncryptForDevice;
Copy link
Member

Choose a reason for hiding this comment

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

Why are we waiting for something different now?

Copy link
Member Author

Choose a reason for hiding this comment

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

There are two steps here:
1: ensure we have olm sessions for each of the devices (this has always been asynchronous as it involves API calls)
2: encrypt the content for each device (which used to be synchronous, but is now async)

In short, we now need to wait for step 2 to happen, whereas previously waiting for step 1 was sufficient

}).then(() => {
// check that it called encryptMessageForDevice with
// appropriate args.
Expand Down
111 changes: 65 additions & 46 deletions src/crypto/OlmDevice.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,10 @@ function OlmDevice(sessionStore) {
this._sessionStore = sessionStore;
this._pickleKey = "DEFAULT_KEY";

let e2eKeys;
const account = new Olm.Account();
try {
_initialise_account(this._sessionStore, this._pickleKey, account);
e2eKeys = JSON.parse(account.identity_keys());
} finally {
account.free();
}

this.deviceCurve25519Key = e2eKeys.curve25519;
this.deviceEd25519Key = e2eKeys.ed25519;
// don't know these until we load the account from storage in init()
this.deviceCurve25519Key = null;
this.deviceEd25519Key = null;
this._maxOneTimeKeys = null;

// we don't bother stashing outboundgroupsessions in the sessionstore -
// instead we keep them here.
Expand All @@ -112,6 +105,32 @@ function OlmDevice(sessionStore) {
this._inboundGroupSessionMessageIndexes = {};
}

/**
* Initialise the OlmAccount. This must be called before any other operations
* on the OlmDevice.
*
* Attempts to load the OlmAccount from localStorage, or creates one if none is
* found.
*
* Reads the device keys from the OlmAccount object.
*/
OlmDevice.prototype.init = async function() {
let e2eKeys;
const account = new Olm.Account();
try {
_initialise_account(this._sessionStore, this._pickleKey, account);
e2eKeys = JSON.parse(account.identity_keys());

this._maxOneTimeKeys = account.max_number_of_one_time_keys();
} finally {
account.free();
}

this.deviceCurve25519Key = e2eKeys.curve25519;
Copy link
Member

Choose a reason for hiding this comment

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

This will throw if _initialise_account throws or if there are no identity keys as e2eKeys will be undefined.

Copy link
Member Author

Choose a reason for hiding this comment

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

if _initialise_account throws, we'll never get here.

OlmAccount.identity_keys() is specced to return a JSON-encoded object, so e2eKeys can't be undefined here.

this.deviceEd25519Key = e2eKeys.ed25519;
};


function _initialise_account(sessionStore, pickleKey, account) {
const e2eAccount = sessionStore.getEndToEndAccount();
if (e2eAccount !== null) {
Expand Down Expand Up @@ -222,9 +241,9 @@ OlmDevice.prototype._getUtility = function(func) {
* Signs a message with the ed25519 key for this account.
*
* @param {string} message message to be signed
* @return {string} base64-encoded signature
* @return {Promise<string>} base64-encoded signature
*/
OlmDevice.prototype.sign = function(message) {
OlmDevice.prototype.sign = async function(message) {
return this._getAccount(function(account) {
return account.sign(message);
});
Expand All @@ -237,7 +256,7 @@ OlmDevice.prototype.sign = function(message) {
* <tt>curve25519</tt>, which is itself an object mapping key id to Curve25519
* key.
*/
OlmDevice.prototype.getOneTimeKeys = function() {
OlmDevice.prototype.getOneTimeKeys = async function() {
return this._getAccount(function(account) {
return JSON.parse(account.one_time_keys());
});
Expand All @@ -250,15 +269,13 @@ OlmDevice.prototype.getOneTimeKeys = function() {
* @return {number} number of keys
*/
OlmDevice.prototype.maxNumberOfOneTimeKeys = function() {
return this._getAccount(function(account) {
return account.max_number_of_one_time_keys();
});
return this._maxOneTimeKeys;
};

/**
* Marks all of the one-time keys as published.
*/
OlmDevice.prototype.markKeysAsPublished = function() {
OlmDevice.prototype.markKeysAsPublished = async function() {
const self = this;
this._getAccount(function(account) {
account.mark_keys_as_published();
Expand All @@ -271,7 +288,7 @@ OlmDevice.prototype.markKeysAsPublished = function() {
*
* @param {number} numKeys number of keys to generate
*/
OlmDevice.prototype.generateOneTimeKeys = function(numKeys) {
OlmDevice.prototype.generateOneTimeKeys = async function(numKeys) {
const self = this;
this._getAccount(function(account) {
account.generate_one_time_keys(numKeys);
Expand All @@ -288,7 +305,7 @@ OlmDevice.prototype.generateOneTimeKeys = function(numKeys) {
* @param {string} theirOneTimeKey remote user's one-time Curve25519 key
* @return {string} sessionId for the outbound session.
*/
OlmDevice.prototype.createOutboundSession = function(
OlmDevice.prototype.createOutboundSession = async function(
theirIdentityKey, theirOneTimeKey,
) {
const self = this;
Expand Down Expand Up @@ -318,7 +335,7 @@ OlmDevice.prototype.createOutboundSession = function(
* @raises {Error} if the received message was not valid (for instance, it
* didn't use a valid one-time key).
*/
OlmDevice.prototype.createInboundSession = function(
OlmDevice.prototype.createInboundSession = async function(
theirDeviceIdentityKey, message_type, ciphertext,
) {
if (message_type !== 0) {
Expand Down Expand Up @@ -353,9 +370,9 @@ OlmDevice.prototype.createInboundSession = function(
*
* @param {string} theirDeviceIdentityKey Curve25519 identity key for the
* remote device
* @return {string[]} a list of known session ids for the device
* @return {Promise<string[]>} a list of known session ids for the device
*/
OlmDevice.prototype.getSessionIdsForDevice = function(theirDeviceIdentityKey) {
OlmDevice.prototype.getSessionIdsForDevice = async function(theirDeviceIdentityKey) {
const sessions = this._sessionStore.getEndToEndSessions(
theirDeviceIdentityKey,
);
Expand All @@ -367,10 +384,10 @@ OlmDevice.prototype.getSessionIdsForDevice = function(theirDeviceIdentityKey) {
*
* @param {string} theirDeviceIdentityKey Curve25519 identity key for the
* remote device
* @return {string?} session id, or null if no established session
* @return {Promise<?string>} session id, or null if no established session
*/
OlmDevice.prototype.getSessionIdForDevice = function(theirDeviceIdentityKey) {
const sessionIds = this.getSessionIdsForDevice(theirDeviceIdentityKey);
OlmDevice.prototype.getSessionIdForDevice = async function(theirDeviceIdentityKey) {
const sessionIds = await this.getSessionIdsForDevice(theirDeviceIdentityKey);
if (sessionIds.length === 0) {
return null;
}
Expand All @@ -390,8 +407,8 @@ OlmDevice.prototype.getSessionIdForDevice = function(theirDeviceIdentityKey) {
* @param {string} deviceIdentityKey Curve25519 identity key for the device
* @return {Array.<{sessionId: string, hasReceivedMessage: Boolean}>}
*/
OlmDevice.prototype.getSessionInfoForDevice = function(deviceIdentityKey) {
const sessionIds = this.getSessionIdsForDevice(deviceIdentityKey);
OlmDevice.prototype.getSessionInfoForDevice = async function(deviceIdentityKey) {
const sessionIds = await this.getSessionIdsForDevice(deviceIdentityKey);
sessionIds.sort();

const info = [];
Expand Down Expand Up @@ -419,9 +436,9 @@ OlmDevice.prototype.getSessionInfoForDevice = function(deviceIdentityKey) {
* @param {string} sessionId the id of the active session
* @param {string} payloadString payload to be encrypted and sent
*
* @return {string} ciphertext
* @return {Promise<string>} ciphertext
*/
OlmDevice.prototype.encryptMessage = function(
OlmDevice.prototype.encryptMessage = async function(
theirDeviceIdentityKey, sessionId, payloadString,
) {
const self = this;
Expand All @@ -444,9 +461,9 @@ OlmDevice.prototype.encryptMessage = function(
* @param {number} message_type message_type field from the received message
* @param {string} ciphertext base64-encoded body from the received message
*
* @return {string} decrypted payload.
* @return {Promise<string>} decrypted payload.
*/
OlmDevice.prototype.decryptMessage = function(
OlmDevice.prototype.decryptMessage = async function(
theirDeviceIdentityKey, sessionId, message_type, ciphertext,
) {
const self = this;
Expand All @@ -468,10 +485,10 @@ OlmDevice.prototype.decryptMessage = function(
* @param {number} message_type message_type field from the received message
* @param {string} ciphertext base64-encoded body from the received message
*
* @return {boolean} true if the received message is a prekey message which matches
* @return {Promise<boolean>} true if the received message is a prekey message which matches
* the given session.
*/
OlmDevice.prototype.matchesSession = function(
OlmDevice.prototype.matchesSession = async function(
theirDeviceIdentityKey, sessionId, message_type, ciphertext,
) {
if (message_type !== 0) {
Expand Down Expand Up @@ -669,7 +686,7 @@ OlmDevice.prototype._getInboundGroupSession = function(
* @param {boolean} exportFormat true if the megolm keys are in export format
* (ie, they lack an ed25519 signature)
*/
OlmDevice.prototype.addInboundGroupSession = function(
OlmDevice.prototype.addInboundGroupSession = async function(
roomId, senderKey, forwardingCurve25519KeyChain,
sessionId, sessionKey, keysClaimed,
exportFormat,
Expand Down Expand Up @@ -727,7 +744,7 @@ OlmDevice.prototype.addInboundGroupSession = function(
*
* @param {module:crypto/OlmDevice.MegolmSessionData} data session data
*/
OlmDevice.prototype.importInboundGroupSession = function(data) {
OlmDevice.prototype.importInboundGroupSession = async function(data) {
/* if we already have this session, consider updating it */
function updateSession(session, sessionData) {
console.log("Update for megolm session " + data.sender_key + "|" +
Expand Down Expand Up @@ -780,11 +797,11 @@ OlmDevice.prototype.importInboundGroupSession = function(data) {
*
* @return {null} the sessionId is unknown
*
* @return {{result: string, senderKey: string,
* @return {Promise<{result: string, senderKey: string,
* forwardingCurve25519KeyChain: Array<string>,
* keysClaimed: Object<string, string>}}
* keysClaimed: Object<string, string>}>}
*/
OlmDevice.prototype.decryptGroupMessage = function(
OlmDevice.prototype.decryptGroupMessage = async function(
roomId, senderKey, sessionId, body,
) {
const self = this;
Expand Down Expand Up @@ -832,9 +849,9 @@ OlmDevice.prototype.decryptGroupMessage = function(
* @param {string} senderKey base64-encoded curve25519 key of the sender
* @param {sring} sessionId session identifier
*
* @returns {boolean} true if we have the keys to this session
* @returns {Promise<boolean>} true if we have the keys to this session
*/
OlmDevice.prototype.hasInboundSessionKeys = function(roomId, senderKey, sessionId) {
OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, sessionId) {
const s = this._sessionStore.getEndToEndInboundGroupSession(
senderKey, sessionId,
);
Expand Down Expand Up @@ -863,14 +880,16 @@ OlmDevice.prototype.hasInboundSessionKeys = function(roomId, senderKey, sessionI
* @param {string} senderKey base64-encoded curve25519 key of the sender
* @param {string} sessionId session identifier
*
* @returns {{chain_index: number, key: string,
* @returns {Promise<{chain_index: number, key: string,
* forwarding_curve25519_key_chain: Array<string>,
* sender_claimed_ed25519_key: string
* }}
* }>}
* details of the session key. The key is a base64-encoded megolm key in
* export format.
*/
OlmDevice.prototype.getInboundGroupSessionKey = function(roomId, senderKey, sessionId) {
OlmDevice.prototype.getInboundGroupSessionKey = async function(
roomId, senderKey, sessionId,
) {
function getKey(session, sessionData) {
const messageIndex = session.first_known_index();

Expand All @@ -896,9 +915,9 @@ OlmDevice.prototype.getInboundGroupSessionKey = function(roomId, senderKey, sess
*
* @param {string} senderKey base64-encoded curve25519 key of the sender
* @param {string} sessionId session identifier
* @return {module:crypto/OlmDevice.MegolmSessionData} exported session data
* @return {Promise<module:crypto/OlmDevice.MegolmSessionData>} exported session data
*/
OlmDevice.prototype.exportInboundGroupSession = function(senderKey, sessionId) {
OlmDevice.prototype.exportInboundGroupSession = async function(senderKey, sessionId) {
const s = this._sessionStore.getEndToEndInboundGroupSession(
senderKey, sessionId,
);
Expand Down
Loading