1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-28 05:03:59 +03:00

Make encryption asynchronous

We're going to need to send out a load of messages to distribute the megolm
keys; as a first step, deal with the asynchronicity this will require.
This commit is contained in:
Richard van der Hoff
2016-08-17 18:18:20 +01:00
parent e0bd05a8c4
commit 32fa51818b
8 changed files with 105 additions and 63 deletions

View File

@@ -822,10 +822,18 @@ function _sendEvent(client, room, event, callback) {
// so that we can handle synchronous and asynchronous exceptions with the
// same code path.
return q().then(function() {
var encryptionPromise = null;
if (client._crypto) {
client._crypto.encryptEventIfNeeded(event, room);
encryptionPromise = client._crypto.encryptEventIfNeeded(event, room);
}
if (encryptionPromise) {
_updatePendingEventStatus(room, event, EventStatus.ENCRYPTING);
encryptionPromise = encryptionPromise.then(function() {
_updatePendingEventStatus(room, event, EventStatus.SENDING);
});
}
return encryptionPromise;
}).then(function() {
var promise;
// this event may be queued
if (client.scheduler) {
@@ -857,10 +865,14 @@ function _sendEvent(client, room, event, callback) {
// the request failed to send.
console.error("Error sending event", err.stack || err);
_updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
try {
_updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
if (callback) {
callback(err);
if (callback) {
callback(err);
}
} catch (err2) {
console.error("Exception in error handler!", err2.stack || err);
}
throw err;
});

View File

@@ -20,6 +20,7 @@ limitations under the License.
*
* @module crypto-algorithms/base
*/
var q = require("q");
var utils = require("../utils");
@@ -43,6 +44,7 @@ module.exports.DECRYPTION_CLASSES = {};
* base type for encryption implementations
*
* @constructor
* @alias module:crypto-algorithms/base.EncryptionAlgorithm
*
* @param {object} params parameters
* @param {string} params.deviceId The identifier for this device.
@@ -50,22 +52,28 @@ module.exports.DECRYPTION_CLASSES = {};
* @param {module:OlmDevice} params.olmDevice olm.js wrapper
* @param {string} params.roomId The ID of the room we will be sending to
*/
module.exports.EncryptionAlgorithm = function(params) {
var EncryptionAlgorithm = function(params) {
this._deviceId = params.deviceId;
this._crypto = params.crypto;
this._olmDevice = params.olmDevice;
this._roomId = params.roomId;
};
/** */
module.exports.EncryptionAlgorithm = EncryptionAlgorithm;
/**
* Initialise this EncryptionAlgorithm instance for a particular room
* Initialise this EncryptionAlgorithm instance for a particular room.
*
* @method module:crypto-algorithms/base.EncryptionAlgorithm#initRoomEncryption
* @abstract
* <p>This will be called once per EncryptionAlgorithm, just after the
* constructor is called.
*
* @param {string[]} roomMembers list of currently-joined users in the room
* @return {module:client.Promise} Promise which resolves when setup is complete
*/
EncryptionAlgorithm.prototype.initRoomEncryption = function(roomMembers) {
return q();
};
/**
* Encrypt a message event
@@ -77,7 +85,7 @@ module.exports.EncryptionAlgorithm = function(params) {
* @param {string} eventType
* @param {object} plaintext event content
*
* @return {object} new event body
* @return {module:client.Promise} Promise which resolves to the new event body
*/

View File

@@ -39,35 +39,33 @@ var MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
*/
function MegolmEncryption(params) {
base.EncryptionAlgorithm.call(this, params);
this._prepPromise = null;
this._outboundSessionId = null;
}
utils.inherits(MegolmEncryption, base.EncryptionAlgorithm);
/**
* @inheritdoc
* @param {string[]} roomMembers list of currently-joined users in the room
* @return {module:client.Promise} Promise which resolves when setup is complete
*/
MegolmEncryption.prototype.initRoomEncryption = function(roomMembers) {
// nothing to do here.
return q();
};
/**
* @private
*
* @return {module:client.Promise} Promise which resolves when setup is
* complete.
*/
MegolmEncryption.prototype._ensureOutboundSession = function() {
if (this._outboundSessionId) {
return;
if (this._prepPromise) {
// prep already in progress
return this._prepPromise;
}
if (this._outboundSessionId) {
// prep already done
return q();
}
var session_id = this._olmDevice.createOutboundGroupSession();
this._outboundSessionId = session_id;
var key = this._olmDevice.getOutboundGroupSessionKey(session_id);
// TODO: initiate key-sharing
console.log(
'Created outbound session. Add with window.mxMatrixClientPeg.' +
'matrixClient._crypto._olmDevice.addInboundGroupSession("' +
@@ -82,8 +80,17 @@ MegolmEncryption.prototype._ensureOutboundSession = function() {
this._roomId, this._olmDevice.deviceCurve25519Key, session_id,
key.key, key.chain_index
);
var self = this;
// TODO: initiate key-sharing
this._prepPromise = q.delay(3000).then(function() {
console.log("woop woop, we totally shared the keys");
self._prepPromise = null;
});
return this._prepPromise;
};
/**
* @inheritdoc
*
@@ -91,30 +98,31 @@ MegolmEncryption.prototype._ensureOutboundSession = function() {
* @param {string} eventType
* @param {object} plaintext event content
*
* @return {object} new event body
* @return {module:client.Promise} Promise which resolves to the new event body
*/
MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
this._ensureOutboundSession();
var self = this;
return this._ensureOutboundSession().then(function() {
var payloadJson = {
room_id: self._roomId,
type: eventType,
content: content
};
var payloadJson = {
room_id: this._roomId,
type: eventType,
content: content
};
var ciphertext = self._olmDevice.encryptGroupMessage(
self._outboundSessionId, JSON.stringify(payloadJson)
);
var ciphertext = this._olmDevice.encryptGroupMessage(
this._outboundSessionId, JSON.stringify(payloadJson)
);
var encryptedContent = {
algorithm: MEGOLM_ALGORITHM,
sender_key: self._olmDevice.deviceCurve25519Key,
body: ciphertext,
session_id: self._outboundSessionId,
signature: "FIXME",
};
var encryptedContent = {
algorithm: MEGOLM_ALGORITHM,
sender_key: this._olmDevice.deviceCurve25519Key,
body: ciphertext,
session_id: this._outboundSessionId,
signature: "FIXME",
};
return encryptedContent;
return encryptedContent;
});
};
/**

View File

@@ -20,6 +20,7 @@ limitations under the License.
*
* @module crypto-algorithms/olm
*/
var q = require('q');
var utils = require("../utils");
@@ -60,7 +61,7 @@ OlmEncryption.prototype.initRoomEncryption = function(roomMembers) {
* @param {string} eventType
* @param {object} plaintext event content
*
* @return {object} new event body
* @return {module:client.Promise} Promise which resolves to the new event body
*/
OlmEncryption.prototype.encryptMessage = function(room, eventType, content) {
if (!room) {
@@ -128,7 +129,7 @@ OlmEncryption.prototype.encryptMessage = function(room, eventType, content) {
sender_key: this._olmDevice.deviceCurve25519Key,
ciphertext: ciphertext
};
return encryptedContent;
return q(encryptedContent);
};
/**

View File

@@ -600,7 +600,7 @@ Crypto.prototype.isSenderKeyVerified = function(userId, algorithm, sender_key) {
* @param {object} config The encryption config for the room.
* @param {string[]} roomMembers userIds of room members to start sessions with
*
* @return {Object} A promise that will resolve when encryption is setup.
* @return {module:client.Promise} A promise that will resolve when encryption is setup.
*/
Crypto.prototype.setRoomEncryption = function(roomId, config, roomMembers) {
// if we already have encryption in this room, we should ignore this event
@@ -727,18 +727,21 @@ Crypto.prototype.isRoomEncrypted = function(roomId) {
*
* @param {module:models/room?} room destination room. Null if the destination
* is not a room we have seen over the sync pipe.
*
* @return {module:client.Promise?} Promise which resolves when the event has been
* encrypted, or null if nothing was needed
*/
Crypto.prototype.encryptEventIfNeeded = function(event, room) {
if (event.isEncrypted()) {
// this event has already been encrypted; this happens if the
// encryption step succeeded, but the send step failed on the first
// attempt.
return;
return null;
}
if (event.getType() !== "m.room.message") {
// we only encrypt m.room.message
return;
return null;
}
var roomId = event.getRoomId();
@@ -755,13 +758,14 @@ Crypto.prototype.encryptEventIfNeeded = function(event, room) {
"configuration event."
);
}
return;
return null;
}
var encryptedContent = alg.encryptMessage(
return alg.encryptMessage(
room, event.getType(), event.getContent()
);
event.makeEncrypted("m.room.encrypted", encryptedContent);
).then(function(encryptedContent) {
event.makeEncrypted("m.room.encrypted", encryptedContent);
});
};
/**

View File

@@ -29,6 +29,10 @@ limitations under the License.
module.exports.EventStatus = {
/** The event was not sent and will no longer be retried. */
NOT_SENT: "not_sent",
/** The message is being encrypted */
ENCRYPTING: "encrypting",
/** The event is in the process of being sent. */
SENDING: "sending",
/** The event is in a queue waiting to be sent. */

View File

@@ -845,8 +845,17 @@ Room.prototype._handleRemoteEcho = function(remoteEvent, localEvent) {
*/
var ALLOWED_TRANSITIONS = {};
ALLOWED_TRANSITIONS[EventStatus.SENDING] =
[EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.SENT];
ALLOWED_TRANSITIONS[EventStatus.ENCRYPTING] = [
EventStatus.SENDING,
EventStatus.NOT_SENT,
];
ALLOWED_TRANSITIONS[EventStatus.SENDING] = [
EventStatus.ENCRYPTING,
EventStatus.QUEUED,
EventStatus.NOT_SENT,
EventStatus.SENT,
];
ALLOWED_TRANSITIONS[EventStatus.QUEUED] =
[EventStatus.SENDING, EventStatus.CANCELLED];

View File

@@ -1,5 +1,4 @@
"use strict";
var q = require("q");
var sdk = require("../..");
var HttpBackend = require("../mock-request");
var utils = require("../test-utils");
@@ -69,8 +68,8 @@ describe("MatrixClient retrying", function() {
expect(ev1.status).toEqual(EventStatus.SENDING);
expect(ev2.status).toEqual(EventStatus.SENDING);
// give the reactor a chance to run, so that ev2 gets queued
q().then(function() {
// the first message should get sent, and the second should get queued
httpBackend.when("PUT", "/send/m.room.message/").check(function(rq) {
// ev2 should now have been queued
expect(ev2.status).toEqual(EventStatus.QUEUED);
@@ -82,12 +81,9 @@ describe("MatrixClient retrying", function() {
// shouldn't be able to cancel the first message yet
expect(function() { client.cancelPendingEvent(ev1); })
.toThrow();
}).respond(400); // fail the first message
// fail the first send
httpBackend.when("PUT", "/send/m.room.message/")
.respond(400);
return httpBackend.flush();
}).then(function() {
httpBackend.flush().then(function() {
expect(ev1.status).toEqual(EventStatus.NOT_SENT);
expect(tl.length).toEqual(1);