You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2026-01-03 23:22:30 +03:00
Merge pull request #510 from matrix-org/rav/async_crypto/crypto_methods
Make a load of methods in the 'Crypto' module asynchronous
This commit is contained in:
@@ -143,7 +143,8 @@ TestClient.prototype.awaitOneTimeKeyUpload = function() {
|
||||
}};
|
||||
});
|
||||
|
||||
return this.httpBackend.flush('/keys/upload', 2).then((flushed) => {
|
||||
// this can take ages
|
||||
return this.httpBackend.flush('/keys/upload', 2, 1000).then((flushed) => {
|
||||
expect(flushed).toEqual(2);
|
||||
return this.oneTimeKeys;
|
||||
});
|
||||
|
||||
@@ -523,7 +523,7 @@ async function _setDeviceVerification(
|
||||
if (!client._crypto) {
|
||||
throw new Error("End-to-End encryption disabled");
|
||||
}
|
||||
const dev = client._crypto.setDeviceVerification(
|
||||
const dev = await client._crypto.setDeviceVerification(
|
||||
userId, deviceId, verified, blocked, known,
|
||||
);
|
||||
client.emit("deviceVerificationChanged", userId, deviceId, dev);
|
||||
@@ -590,14 +590,13 @@ MatrixClient.prototype.isEventSenderVerified = async function(event) {
|
||||
* Enable end-to-end encryption for a room.
|
||||
* @param {string} roomId The room ID to enable encryption in.
|
||||
* @param {object} config The encryption config for the room.
|
||||
* @return {Object} A promise that will resolve when encryption is setup.
|
||||
* @return {Promise} A promise that will resolve when encryption is set up.
|
||||
*/
|
||||
MatrixClient.prototype.setRoomEncryption = function(roomId, config) {
|
||||
if (!this._crypto) {
|
||||
throw new Error("End-to-End encryption disabled");
|
||||
}
|
||||
this._crypto.setRoomEncryption(roomId, config);
|
||||
return Promise.resolve();
|
||||
return this._crypto.setRoomEncryption(roomId, config);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -632,12 +631,15 @@ MatrixClient.prototype.exportRoomKeys = function() {
|
||||
* Import a list of room keys previously exported by exportRoomKeys
|
||||
*
|
||||
* @param {Object[]} keys a list of session export objects
|
||||
*
|
||||
* @return {module:client.Promise} a promise which resolves when the keys
|
||||
* have been imported
|
||||
*/
|
||||
MatrixClient.prototype.importRoomKeys = async function(keys) {
|
||||
MatrixClient.prototype.importRoomKeys = function(keys) {
|
||||
if (!this._crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
this._crypto.importRoomKeys(keys);
|
||||
return this._crypto.importRoomKeys(keys);
|
||||
};
|
||||
|
||||
// Room ops
|
||||
|
||||
@@ -104,6 +104,8 @@ function Crypto(baseApis, sessionStore, userId, deviceId,
|
||||
// we received in the current sync.
|
||||
this._receivedRoomKeyRequests = [];
|
||||
this._receivedRoomKeyRequestCancellations = [];
|
||||
// true if we are currently processing received room key requests
|
||||
this._processingRoomKeyRequests = false;
|
||||
|
||||
let myDevices = this._sessionStore.getEndToEndDevicesForUser(
|
||||
this._userId,
|
||||
@@ -229,14 +231,15 @@ Crypto.prototype.uploadDeviceKeys = function() {
|
||||
keys: crypto._deviceKeys,
|
||||
user_id: userId,
|
||||
};
|
||||
crypto._signObject(deviceKeys);
|
||||
|
||||
return crypto._baseApis.uploadKeysRequest({
|
||||
device_keys: deviceKeys,
|
||||
}, {
|
||||
// for now, we set the device id explicitly, as we may not be using the
|
||||
// same one as used in login.
|
||||
device_id: deviceId,
|
||||
return crypto._signObject(deviceKeys).then(() => {
|
||||
crypto._baseApis.uploadKeysRequest({
|
||||
device_keys: deviceKeys,
|
||||
}, {
|
||||
// for now, we set the device id explicitly, as we may not be using the
|
||||
// same one as used in login.
|
||||
device_id: deviceId,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -353,30 +356,34 @@ function _maybeUploadOneTimeKeys(crypto) {
|
||||
}
|
||||
|
||||
// returns a promise which resolves to the response
|
||||
function _uploadOneTimeKeys(crypto) {
|
||||
async function _uploadOneTimeKeys(crypto) {
|
||||
const oneTimeKeys = crypto._olmDevice.getOneTimeKeys();
|
||||
const oneTimeJson = {};
|
||||
|
||||
const promises = [];
|
||||
|
||||
for (const keyId in oneTimeKeys.curve25519) {
|
||||
if (oneTimeKeys.curve25519.hasOwnProperty(keyId)) {
|
||||
const k = {
|
||||
key: oneTimeKeys.curve25519[keyId],
|
||||
};
|
||||
crypto._signObject(k);
|
||||
oneTimeJson["signed_curve25519:" + keyId] = k;
|
||||
promises.push(crypto._signObject(k));
|
||||
}
|
||||
}
|
||||
|
||||
return crypto._baseApis.uploadKeysRequest({
|
||||
await Promise.all(promises);
|
||||
|
||||
const res = await crypto._baseApis.uploadKeysRequest({
|
||||
one_time_keys: oneTimeJson,
|
||||
}, {
|
||||
// for now, we set the device id explicitly, as we may not be using the
|
||||
// same one as used in login.
|
||||
device_id: crypto._deviceId,
|
||||
}).then(function(res) {
|
||||
crypto._olmDevice.markKeysAsPublished();
|
||||
return res;
|
||||
});
|
||||
|
||||
crypto._olmDevice.markKeysAsPublished();
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,10 +439,11 @@ Crypto.prototype.getStoredDevice = function(userId, deviceId) {
|
||||
* @param {?boolean} known whether to mark that the user has been made aware of
|
||||
* the existence of this device. Null to leave unchanged
|
||||
*
|
||||
* @return {module:crypto/deviceinfo} updated DeviceInfo
|
||||
* @return {Promise<module:crypto/deviceinfo>} updated DeviceInfo
|
||||
*/
|
||||
Crypto.prototype.setDeviceVerification = function(userId, deviceId, verified,
|
||||
blocked, known) {
|
||||
Crypto.prototype.setDeviceVerification = async function(
|
||||
userId, deviceId, verified, blocked, known,
|
||||
) {
|
||||
const devices = this._sessionStore.getEndToEndDevicesForUser(userId);
|
||||
if (!devices || !devices[deviceId]) {
|
||||
throw new Error("Unknown device " + userId + ":" + deviceId);
|
||||
@@ -482,9 +490,9 @@ Crypto.prototype.setDeviceVerification = function(userId, deviceId, verified,
|
||||
*
|
||||
* @param {string} userId id of user to inspect
|
||||
*
|
||||
* @return {Object.<string, {deviceIdKey: string, sessions: object[]}>}
|
||||
* @return {Promise<Object.<string, {deviceIdKey: string, sessions: object[]}>>}
|
||||
*/
|
||||
Crypto.prototype.getOlmSessionsForUser = function(userId) {
|
||||
Crypto.prototype.getOlmSessionsForUser = async function(userId) {
|
||||
const devices = this.getStoredDevicesForUser(userId) || [];
|
||||
const result = {};
|
||||
for (let j = 0; j < devices.length; ++j) {
|
||||
@@ -572,7 +580,7 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) {
|
||||
* @param {boolean=} inhibitDeviceQuery true to suppress device list query for
|
||||
* users in the room (for now)
|
||||
*/
|
||||
Crypto.prototype.setRoomEncryption = function(roomId, config, inhibitDeviceQuery) {
|
||||
Crypto.prototype.setRoomEncryption = async function(roomId, config, inhibitDeviceQuery) {
|
||||
// if we already have encryption in this room, we should ignore this event
|
||||
// (for now at least. maybe we should alert the user somehow?)
|
||||
const existingConfig = this._sessionStore.getEndToEndRoom(roomId);
|
||||
@@ -698,17 +706,20 @@ Crypto.prototype.exportRoomKeys = function() {
|
||||
* Import a list of room keys previously exported by exportRoomKeys
|
||||
*
|
||||
* @param {Object[]} keys a list of session export objects
|
||||
* @return {module:client.Promise} a promise which resolves once the keys have been imported
|
||||
*/
|
||||
Crypto.prototype.importRoomKeys = function(keys) {
|
||||
keys.map((session) => {
|
||||
if (!session.room_id || !session.algorithm) {
|
||||
console.warn("ignoring session entry with missing fields", session);
|
||||
return;
|
||||
}
|
||||
return Promise.map(
|
||||
keys, (key) => {
|
||||
if (!key.room_id || !key.algorithm) {
|
||||
console.warn("ignoring room key entry with missing fields", key);
|
||||
return;
|
||||
}
|
||||
|
||||
const alg = this._getRoomDecryptor(session.room_id, session.algorithm);
|
||||
alg.importRoomKey(session);
|
||||
});
|
||||
const alg = this._getRoomDecryptor(key.room_id, key.algorithm);
|
||||
alg.importRoomKey(key);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -831,7 +842,7 @@ Crypto.prototype.onCryptoEvent = async function(event) {
|
||||
try {
|
||||
// inhibit the device list refresh for now - it will happen once we've
|
||||
// finished processing the sync, in _onSyncCompleted.
|
||||
this.setRoomEncryption(roomId, content, true);
|
||||
await this.setRoomEncryption(roomId, content, true);
|
||||
} catch (e) {
|
||||
console.error("Error configuring encryption in room " + roomId +
|
||||
":", e);
|
||||
@@ -1051,79 +1062,124 @@ Crypto.prototype._onRoomKeyRequestEvent = function(event) {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Crypto.prototype._processReceivedRoomKeyRequests = function() {
|
||||
const requests = this._receivedRoomKeyRequests;
|
||||
this._receivedRoomKeyRequests = [];
|
||||
for (const req of requests) {
|
||||
const userId = req.userId;
|
||||
const deviceId = req.deviceId;
|
||||
Crypto.prototype._processReceivedRoomKeyRequests = async function() {
|
||||
if (this._processingRoomKeyRequests) {
|
||||
// we're still processing last time's requests; keep queuing new ones
|
||||
// up for now.
|
||||
return;
|
||||
}
|
||||
this._processingRoomKeyRequests = true;
|
||||
|
||||
const body = req.requestBody;
|
||||
const roomId = body.room_id;
|
||||
const alg = body.algorithm;
|
||||
try {
|
||||
// we need to grab and clear the queues in the synchronous bit of this method,
|
||||
// so that we don't end up racing with the next /sync.
|
||||
const requests = this._receivedRoomKeyRequests;
|
||||
this._receivedRoomKeyRequests = [];
|
||||
const cancellations = this._receivedRoomKeyRequestCancellations;
|
||||
this._receivedRoomKeyRequestCancellations = [];
|
||||
|
||||
console.log(`m.room_key_request from ${userId}:${deviceId}` +
|
||||
// Process all of the requests, *then* all of the cancellations.
|
||||
//
|
||||
// This makes sure that if we get a request and its cancellation in the
|
||||
// same /sync result, then we process the request before the
|
||||
// cancellation (and end up with a cancelled request), rather than the
|
||||
// cancellation before the request (and end up with an outstanding
|
||||
// request which should have been cancelled.)
|
||||
await Promise.map(
|
||||
requests, (req) =>
|
||||
this._processReceivedRoomKeyRequest(req),
|
||||
);
|
||||
await Promise.map(
|
||||
cancellations, (cancellation) =>
|
||||
this._processReceivedRoomKeyRequestCancellation(cancellation),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(`Error processing room key requsts: ${e}`);
|
||||
} finally {
|
||||
this._processingRoomKeyRequests = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for processReceivedRoomKeyRequests
|
||||
*
|
||||
* @param {IncomingRoomKeyRequest} req
|
||||
*/
|
||||
Crypto.prototype._processReceivedRoomKeyRequest = async function(req) {
|
||||
const userId = req.userId;
|
||||
const deviceId = req.deviceId;
|
||||
|
||||
const body = req.requestBody;
|
||||
const roomId = body.room_id;
|
||||
const alg = body.algorithm;
|
||||
|
||||
console.log(`m.room_key_request from ${userId}:${deviceId}` +
|
||||
` for ${roomId} / ${body.session_id} (id ${req.requestId})`);
|
||||
|
||||
if (userId !== this._userId) {
|
||||
// TODO: determine if we sent this device the keys already: in
|
||||
// which case we can do so again.
|
||||
console.log("Ignoring room key request from other user for now");
|
||||
return;
|
||||
}
|
||||
|
||||
// todo: should we queue up requests we don't yet have keys for,
|
||||
// in case they turn up later?
|
||||
|
||||
// if we don't have a decryptor for this room/alg, we don't have
|
||||
// the keys for the requested events, and can drop the requests.
|
||||
if (!this._roomDecryptors[roomId]) {
|
||||
console.log(`room key request for unencrypted room ${roomId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const decryptor = this._roomDecryptors[roomId][alg];
|
||||
if (!decryptor) {
|
||||
console.log(`room key request for unknown alg ${alg} in room ${roomId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!decryptor.hasKeysForKeyRequest(req)) {
|
||||
console.log(
|
||||
`room key request for unknown session ${roomId} / ` +
|
||||
body.session_id,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
req.share = () => {
|
||||
decryptor.shareKeysWithDevice(req);
|
||||
};
|
||||
|
||||
// if the device is is verified already, share the keys
|
||||
const device = this._deviceList.getStoredDevice(userId, deviceId);
|
||||
if (device && device.isVerified()) {
|
||||
console.log('device is already verified: sharing keys');
|
||||
req.share();
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit("crypto.roomKeyRequest", req);
|
||||
if (userId !== this._userId) {
|
||||
// TODO: determine if we sent this device the keys already: in
|
||||
// which case we can do so again.
|
||||
console.log("Ignoring room key request from other user for now");
|
||||
return;
|
||||
}
|
||||
|
||||
const cancellations = this._receivedRoomKeyRequestCancellations;
|
||||
this._receivedRoomKeyRequestCancellations = [];
|
||||
for (const cancellation of cancellations) {
|
||||
// todo: should we queue up requests we don't yet have keys for,
|
||||
// in case they turn up later?
|
||||
|
||||
// if we don't have a decryptor for this room/alg, we don't have
|
||||
// the keys for the requested events, and can drop the requests.
|
||||
if (!this._roomDecryptors[roomId]) {
|
||||
console.log(`room key request for unencrypted room ${roomId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const decryptor = this._roomDecryptors[roomId][alg];
|
||||
if (!decryptor) {
|
||||
console.log(`room key request for unknown alg ${alg} in room ${roomId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!decryptor.hasKeysForKeyRequest(req)) {
|
||||
console.log(
|
||||
`m.room_key_request cancellation for ${cancellation.userId}:` +
|
||||
`${cancellation.deviceId} (id ${cancellation.requestId})`,
|
||||
`room key request for unknown session ${roomId} / ` +
|
||||
body.session_id,
|
||||
);
|
||||
|
||||
// we should probably only notify the app of cancellations we told it
|
||||
// about, but we don't currently have a record of that, so we just pass
|
||||
// everything through.
|
||||
this.emit("crypto.roomKeyRequestCancellation", cancellation);
|
||||
return;
|
||||
}
|
||||
|
||||
req.share = () => {
|
||||
decryptor.shareKeysWithDevice(req);
|
||||
};
|
||||
|
||||
// if the device is is verified already, share the keys
|
||||
const device = this._deviceList.getStoredDevice(userId, deviceId);
|
||||
if (device && device.isVerified()) {
|
||||
console.log('device is already verified: sharing keys');
|
||||
req.share();
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit("crypto.roomKeyRequest", req);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper for processReceivedRoomKeyRequests
|
||||
*
|
||||
* @param {IncomingRoomKeyRequestCancellation} cancellation
|
||||
*/
|
||||
Crypto.prototype._processReceivedRoomKeyRequestCancellation = async function(
|
||||
cancellation,
|
||||
) {
|
||||
console.log(
|
||||
`m.room_key_request cancellation for ${cancellation.userId}:` +
|
||||
`${cancellation.deviceId} (id ${cancellation.requestId})`,
|
||||
);
|
||||
|
||||
// we should probably only notify the app of cancellations we told it
|
||||
// about, but we don't currently have a record of that, so we just pass
|
||||
// everything through.
|
||||
this.emit("crypto.roomKeyRequestCancellation", cancellation);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1187,7 +1243,7 @@ Crypto.prototype._getRoomDecryptor = function(roomId, algorithm) {
|
||||
*
|
||||
* @param {Object} obj Object to which we will add a 'signatures' property
|
||||
*/
|
||||
Crypto.prototype._signObject = function(obj) {
|
||||
Crypto.prototype._signObject = async function(obj) {
|
||||
const sigs = {};
|
||||
sigs[this._userId] = {};
|
||||
sigs[this._userId]["ed25519:" + this._deviceId] =
|
||||
|
||||
Reference in New Issue
Block a user