You've already forked matrix-js-sdk
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:
@@ -822,10 +822,18 @@ function _sendEvent(client, room, event, callback) {
|
|||||||
// so that we can handle synchronous and asynchronous exceptions with the
|
// so that we can handle synchronous and asynchronous exceptions with the
|
||||||
// same code path.
|
// same code path.
|
||||||
return q().then(function() {
|
return q().then(function() {
|
||||||
|
var encryptionPromise = null;
|
||||||
if (client._crypto) {
|
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;
|
var promise;
|
||||||
// this event may be queued
|
// this event may be queued
|
||||||
if (client.scheduler) {
|
if (client.scheduler) {
|
||||||
@@ -857,10 +865,14 @@ function _sendEvent(client, room, event, callback) {
|
|||||||
// the request failed to send.
|
// the request failed to send.
|
||||||
console.error("Error sending event", err.stack || err);
|
console.error("Error sending event", err.stack || err);
|
||||||
|
|
||||||
_updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
|
try {
|
||||||
|
_updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(err);
|
callback(err);
|
||||||
|
}
|
||||||
|
} catch (err2) {
|
||||||
|
console.error("Exception in error handler!", err2.stack || err);
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ limitations under the License.
|
|||||||
*
|
*
|
||||||
* @module crypto-algorithms/base
|
* @module crypto-algorithms/base
|
||||||
*/
|
*/
|
||||||
|
var q = require("q");
|
||||||
|
|
||||||
var utils = require("../utils");
|
var utils = require("../utils");
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ module.exports.DECRYPTION_CLASSES = {};
|
|||||||
* base type for encryption implementations
|
* base type for encryption implementations
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
|
* @alias module:crypto-algorithms/base.EncryptionAlgorithm
|
||||||
*
|
*
|
||||||
* @param {object} params parameters
|
* @param {object} params parameters
|
||||||
* @param {string} params.deviceId The identifier for this device.
|
* @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 {module:OlmDevice} params.olmDevice olm.js wrapper
|
||||||
* @param {string} params.roomId The ID of the room we will be sending to
|
* @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._deviceId = params.deviceId;
|
||||||
this._crypto = params.crypto;
|
this._crypto = params.crypto;
|
||||||
this._olmDevice = params.olmDevice;
|
this._olmDevice = params.olmDevice;
|
||||||
this._roomId = params.roomId;
|
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
|
* <p>This will be called once per EncryptionAlgorithm, just after the
|
||||||
* @abstract
|
* constructor is called.
|
||||||
*
|
*
|
||||||
* @param {string[]} roomMembers list of currently-joined users in the room
|
* @param {string[]} roomMembers list of currently-joined users in the room
|
||||||
* @return {module:client.Promise} Promise which resolves when setup is complete
|
* @return {module:client.Promise} Promise which resolves when setup is complete
|
||||||
*/
|
*/
|
||||||
|
EncryptionAlgorithm.prototype.initRoomEncryption = function(roomMembers) {
|
||||||
|
return q();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt a message event
|
* Encrypt a message event
|
||||||
@@ -77,7 +85,7 @@ module.exports.EncryptionAlgorithm = function(params) {
|
|||||||
* @param {string} eventType
|
* @param {string} eventType
|
||||||
* @param {object} plaintext event content
|
* @param {object} plaintext event content
|
||||||
*
|
*
|
||||||
* @return {object} new event body
|
* @return {module:client.Promise} Promise which resolves to the new event body
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,35 +39,33 @@ var MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
|
|||||||
*/
|
*/
|
||||||
function MegolmEncryption(params) {
|
function MegolmEncryption(params) {
|
||||||
base.EncryptionAlgorithm.call(this, params);
|
base.EncryptionAlgorithm.call(this, params);
|
||||||
|
this._prepPromise = null;
|
||||||
this._outboundSessionId = null;
|
this._outboundSessionId = null;
|
||||||
}
|
}
|
||||||
utils.inherits(MegolmEncryption, base.EncryptionAlgorithm);
|
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
|
* @private
|
||||||
|
*
|
||||||
|
* @return {module:client.Promise} Promise which resolves when setup is
|
||||||
|
* complete.
|
||||||
*/
|
*/
|
||||||
MegolmEncryption.prototype._ensureOutboundSession = function() {
|
MegolmEncryption.prototype._ensureOutboundSession = function() {
|
||||||
if (this._outboundSessionId) {
|
if (this._prepPromise) {
|
||||||
return;
|
// prep already in progress
|
||||||
|
return this._prepPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._outboundSessionId) {
|
||||||
|
// prep already done
|
||||||
|
return q();
|
||||||
|
}
|
||||||
|
|
||||||
var session_id = this._olmDevice.createOutboundGroupSession();
|
var session_id = this._olmDevice.createOutboundGroupSession();
|
||||||
this._outboundSessionId = session_id;
|
this._outboundSessionId = session_id;
|
||||||
|
|
||||||
var key = this._olmDevice.getOutboundGroupSessionKey(session_id);
|
var key = this._olmDevice.getOutboundGroupSessionKey(session_id);
|
||||||
|
|
||||||
// TODO: initiate key-sharing
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
'Created outbound session. Add with window.mxMatrixClientPeg.' +
|
'Created outbound session. Add with window.mxMatrixClientPeg.' +
|
||||||
'matrixClient._crypto._olmDevice.addInboundGroupSession("' +
|
'matrixClient._crypto._olmDevice.addInboundGroupSession("' +
|
||||||
@@ -82,8 +80,17 @@ MegolmEncryption.prototype._ensureOutboundSession = function() {
|
|||||||
this._roomId, this._olmDevice.deviceCurve25519Key, session_id,
|
this._roomId, this._olmDevice.deviceCurve25519Key, session_id,
|
||||||
key.key, key.chain_index
|
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
|
* @inheritdoc
|
||||||
*
|
*
|
||||||
@@ -91,30 +98,31 @@ MegolmEncryption.prototype._ensureOutboundSession = function() {
|
|||||||
* @param {string} eventType
|
* @param {string} eventType
|
||||||
* @param {object} plaintext event content
|
* @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) {
|
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 = {
|
var ciphertext = self._olmDevice.encryptGroupMessage(
|
||||||
room_id: this._roomId,
|
self._outboundSessionId, JSON.stringify(payloadJson)
|
||||||
type: eventType,
|
);
|
||||||
content: content
|
|
||||||
};
|
|
||||||
|
|
||||||
var ciphertext = this._olmDevice.encryptGroupMessage(
|
var encryptedContent = {
|
||||||
this._outboundSessionId, JSON.stringify(payloadJson)
|
algorithm: MEGOLM_ALGORITHM,
|
||||||
);
|
sender_key: self._olmDevice.deviceCurve25519Key,
|
||||||
|
body: ciphertext,
|
||||||
|
session_id: self._outboundSessionId,
|
||||||
|
signature: "FIXME",
|
||||||
|
};
|
||||||
|
|
||||||
var encryptedContent = {
|
return encryptedContent;
|
||||||
algorithm: MEGOLM_ALGORITHM,
|
});
|
||||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
|
||||||
body: ciphertext,
|
|
||||||
session_id: this._outboundSessionId,
|
|
||||||
signature: "FIXME",
|
|
||||||
};
|
|
||||||
|
|
||||||
return encryptedContent;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ limitations under the License.
|
|||||||
*
|
*
|
||||||
* @module crypto-algorithms/olm
|
* @module crypto-algorithms/olm
|
||||||
*/
|
*/
|
||||||
|
var q = require('q');
|
||||||
|
|
||||||
var utils = require("../utils");
|
var utils = require("../utils");
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ OlmEncryption.prototype.initRoomEncryption = function(roomMembers) {
|
|||||||
* @param {string} eventType
|
* @param {string} eventType
|
||||||
* @param {object} plaintext event content
|
* @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) {
|
OlmEncryption.prototype.encryptMessage = function(room, eventType, content) {
|
||||||
if (!room) {
|
if (!room) {
|
||||||
@@ -128,7 +129,7 @@ OlmEncryption.prototype.encryptMessage = function(room, eventType, content) {
|
|||||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||||
ciphertext: ciphertext
|
ciphertext: ciphertext
|
||||||
};
|
};
|
||||||
return encryptedContent;
|
return q(encryptedContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -600,7 +600,7 @@ Crypto.prototype.isSenderKeyVerified = function(userId, algorithm, sender_key) {
|
|||||||
* @param {object} config The encryption config for the room.
|
* @param {object} config The encryption config for the room.
|
||||||
* @param {string[]} roomMembers userIds of room members to start sessions with
|
* @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) {
|
Crypto.prototype.setRoomEncryption = function(roomId, config, roomMembers) {
|
||||||
// if we already have encryption in this room, we should ignore this event
|
// 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
|
* @param {module:models/room?} room destination room. Null if the destination
|
||||||
* is not a room we have seen over the sync pipe.
|
* 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) {
|
Crypto.prototype.encryptEventIfNeeded = function(event, room) {
|
||||||
if (event.isEncrypted()) {
|
if (event.isEncrypted()) {
|
||||||
// this event has already been encrypted; this happens if the
|
// this event has already been encrypted; this happens if the
|
||||||
// encryption step succeeded, but the send step failed on the first
|
// encryption step succeeded, but the send step failed on the first
|
||||||
// attempt.
|
// attempt.
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.getType() !== "m.room.message") {
|
if (event.getType() !== "m.room.message") {
|
||||||
// we only encrypt m.room.message
|
// we only encrypt m.room.message
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var roomId = event.getRoomId();
|
var roomId = event.getRoomId();
|
||||||
@@ -755,13 +758,14 @@ Crypto.prototype.encryptEventIfNeeded = function(event, room) {
|
|||||||
"configuration event."
|
"configuration event."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var encryptedContent = alg.encryptMessage(
|
return alg.encryptMessage(
|
||||||
room, event.getType(), event.getContent()
|
room, event.getType(), event.getContent()
|
||||||
);
|
).then(function(encryptedContent) {
|
||||||
event.makeEncrypted("m.room.encrypted", encryptedContent);
|
event.makeEncrypted("m.room.encrypted", encryptedContent);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ limitations under the License.
|
|||||||
module.exports.EventStatus = {
|
module.exports.EventStatus = {
|
||||||
/** The event was not sent and will no longer be retried. */
|
/** The event was not sent and will no longer be retried. */
|
||||||
NOT_SENT: "not_sent",
|
NOT_SENT: "not_sent",
|
||||||
|
|
||||||
|
/** The message is being encrypted */
|
||||||
|
ENCRYPTING: "encrypting",
|
||||||
|
|
||||||
/** The event is in the process of being sent. */
|
/** The event is in the process of being sent. */
|
||||||
SENDING: "sending",
|
SENDING: "sending",
|
||||||
/** The event is in a queue waiting to be sent. */
|
/** The event is in a queue waiting to be sent. */
|
||||||
|
|||||||
@@ -845,8 +845,17 @@ Room.prototype._handleRemoteEcho = function(remoteEvent, localEvent) {
|
|||||||
*/
|
*/
|
||||||
var ALLOWED_TRANSITIONS = {};
|
var ALLOWED_TRANSITIONS = {};
|
||||||
|
|
||||||
ALLOWED_TRANSITIONS[EventStatus.SENDING] =
|
ALLOWED_TRANSITIONS[EventStatus.ENCRYPTING] = [
|
||||||
[EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.SENT];
|
EventStatus.SENDING,
|
||||||
|
EventStatus.NOT_SENT,
|
||||||
|
];
|
||||||
|
|
||||||
|
ALLOWED_TRANSITIONS[EventStatus.SENDING] = [
|
||||||
|
EventStatus.ENCRYPTING,
|
||||||
|
EventStatus.QUEUED,
|
||||||
|
EventStatus.NOT_SENT,
|
||||||
|
EventStatus.SENT,
|
||||||
|
];
|
||||||
|
|
||||||
ALLOWED_TRANSITIONS[EventStatus.QUEUED] =
|
ALLOWED_TRANSITIONS[EventStatus.QUEUED] =
|
||||||
[EventStatus.SENDING, EventStatus.CANCELLED];
|
[EventStatus.SENDING, EventStatus.CANCELLED];
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
var q = require("q");
|
|
||||||
var sdk = require("../..");
|
var sdk = require("../..");
|
||||||
var HttpBackend = require("../mock-request");
|
var HttpBackend = require("../mock-request");
|
||||||
var utils = require("../test-utils");
|
var utils = require("../test-utils");
|
||||||
@@ -69,8 +68,8 @@ describe("MatrixClient retrying", function() {
|
|||||||
expect(ev1.status).toEqual(EventStatus.SENDING);
|
expect(ev1.status).toEqual(EventStatus.SENDING);
|
||||||
expect(ev2.status).toEqual(EventStatus.SENDING);
|
expect(ev2.status).toEqual(EventStatus.SENDING);
|
||||||
|
|
||||||
// give the reactor a chance to run, so that ev2 gets queued
|
// the first message should get sent, and the second should get queued
|
||||||
q().then(function() {
|
httpBackend.when("PUT", "/send/m.room.message/").check(function(rq) {
|
||||||
// ev2 should now have been queued
|
// ev2 should now have been queued
|
||||||
expect(ev2.status).toEqual(EventStatus.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
|
// shouldn't be able to cancel the first message yet
|
||||||
expect(function() { client.cancelPendingEvent(ev1); })
|
expect(function() { client.cancelPendingEvent(ev1); })
|
||||||
.toThrow();
|
.toThrow();
|
||||||
|
}).respond(400); // fail the first message
|
||||||
|
|
||||||
// fail the first send
|
httpBackend.flush().then(function() {
|
||||||
httpBackend.when("PUT", "/send/m.room.message/")
|
|
||||||
.respond(400);
|
|
||||||
return httpBackend.flush();
|
|
||||||
}).then(function() {
|
|
||||||
expect(ev1.status).toEqual(EventStatus.NOT_SENT);
|
expect(ev1.status).toEqual(EventStatus.NOT_SENT);
|
||||||
expect(tl.length).toEqual(1);
|
expect(tl.length).toEqual(1);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user