diff --git a/lib/crypto-deviceinfo.js b/lib/crypto-deviceinfo.js index c7d08b024..ac18996c9 100644 --- a/lib/crypto-deviceinfo.js +++ b/lib/crypto-deviceinfo.js @@ -94,6 +94,15 @@ DeviceInfo.prototype.getFingerprint = function() { return this.keys["ed25519:" + this.deviceId]; }; +/** + * Get the identity key for this device (ie, the Curve25519 key) + * + * @return {string} base64-encoded identity key of this device + */ +DeviceInfo.prototype.getIdentityKey = function() { + return this.keys["curve25519:" + this.deviceId]; +}; + /** * Get the configured display name for this device, if any * diff --git a/lib/crypto.js b/lib/crypto.js index 34e532ce0..ab1da57c6 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -546,56 +546,81 @@ Crypto.prototype.setRoomEncryption = function(roomId, config, roomMembers) { return alg.initRoomEncryption(roomMembers); }; + +/** + * @typedef {Object} module:crypto~OlmSessionResult + * @property {module:crypto-deviceinfo} device device info + * @property {string?} sessionId base64 olm session id; null if no session + * could be established + */ + /** * Try to make sure we have established olm sessions for the given users. * * @param {string[]} users list of user ids * * @return {module:client.Promise} resolves once the sessions are complete, to - * an object with keys missingUsers (a list of users with no known - * olm devices), and missingDevices a list of olm devices with no - * known one-time keys. + * an Object mapping from userId to deviceId to + * {@link module:crypto~OlmSessionResult} */ Crypto.prototype.ensureOlmSessionsForUsers = function(users) { - var devicesWithoutSession = []; - var userWithoutDevices = []; + var devicesWithoutSession = [ + // [userId, deviceId, deviceInfo], ... + ]; + var result = {}; + for (var i = 0; i < users.length; ++i) { var userId = users[i]; - var devices = this._sessionStore.getEndToEndDevicesForUser(userId); - if (!devices) { - userWithoutDevices.push(userId); - } else { - for (var deviceId in devices) { - if (devices.hasOwnProperty(deviceId)) { - var keys = devices[deviceId]; - var key = keys.keys["curve25519:" + deviceId]; - if (key == this._olmDevice.deviceCurve25519Key) { - continue; - } - if (!this._sessionStore.getEndToEndSessions(key)) { - devicesWithoutSession.push([userId, deviceId, key]); - } - } + result[userId] = {}; + + var devices = this.getStoredDevicesForUser(userId); + for (var j = 0; j < devices.length; ++j) { + var deviceInfo = devices[j]; + var deviceId = deviceInfo.deviceId; + + var key = deviceInfo.getIdentityKey(); + if (key == this._olmDevice.deviceCurve25519Key) { + // don't bother setting up session to ourself + continue; } + if (deviceInfo.verified == DeviceVerification.BLOCKED) { + // don't bother setting up sessions with blocked users + continue; + } + + var sessionId = this._olmDevice.getSessionIdForDevice(key); + if (sessionId === null) { + devicesWithoutSession.push([userId, deviceId, deviceInfo]); + } + result[userId][deviceId] = { + device: deviceInfo, + sessionId: sessionId, + }; } } if (devicesWithoutSession.length === 0) { - return q({ - missingUsers: userWithoutDevices, - missingDevices: [] - }); + return q(result); } + // TODO: this has a race condition - if we try to send another message + // while we are claiming a key, we will end up claiming two and setting up + // two sessions. + // + // That should eventually resolve itself, but it's poor form. + var self = this; return this._baseApis.claimOneTimeKeys( devicesWithoutSession ).then(function(res) { - var missing = {}; - for (i = 0; i < devicesWithoutSession.length; ++i) { + for (var i = 0; i < devicesWithoutSession.length; ++i) { var device = devicesWithoutSession[i]; - var userRes = res.one_time_keys[device[0]] || {}; - var deviceRes = userRes[device[1]]; + var userId = device[0]; + var deviceId = device[1]; + var deviceInfo = device[2]; + + var userRes = res.one_time_keys[userId] || {}; + var deviceRes = userRes[deviceId]; var oneTimeKey; for (var keyId in deviceRes) { if (keyId.indexOf("curve25519:") === 0) { @@ -604,20 +629,18 @@ Crypto.prototype.ensureOlmSessionsForUsers = function(users) { } if (oneTimeKey) { var sid = self._olmDevice.createOutboundSession( - device[2], oneTimeKey + deviceInfo.getIdentityKey(), oneTimeKey ); console.log("Started new sessionid " + sid + - " for device " + device[2]); + " for device " + userId + ":" + deviceId); + + result[userId][deviceId].sessionId = sid; } else { - missing[device[0]] = missing[device[0]] || []; - missing[device[0]].push([device[1]]); + console.warn("No one-time keys for device " + + userId + ":" + deviceId); } } - - return { - missingUsers: userWithoutDevices, - missingDevices: missing - }; + return result; }); }; diff --git a/spec/integ/matrix-client-crypto.spec.js b/spec/integ/matrix-client-crypto.spec.js index 6d2325518..a2b805e18 100644 --- a/spec/integ/matrix-client-crypto.spec.js +++ b/spec/integ/matrix-client-crypto.spec.js @@ -213,8 +213,9 @@ function aliEnablesEncryption() { var p = aliClient.setRoomEncryption(roomId, { algorithm: "m.olm.v1.curve25519-aes-sha2", }).then(function(res) { - expect(res.missingUsers).toEqual([]); - expect(res.missingDevices).toEqual({}); + expect(res[aliUserId]).toEqual({}); + expect(res[bobUserId][bobDeviceId].device).toBeDefined(); + expect(res[bobUserId][bobDeviceId].sessionId).toBeDefined(); expect(aliClient.isRoomEncrypted(roomId)).toBeTruthy(); }); aliHttpBackend.flush(); @@ -226,8 +227,9 @@ function bobEnablesEncryption() { return bobClient.setRoomEncryption(roomId, { algorithm: "m.olm.v1.curve25519-aes-sha2", }).then(function(res) { - expect(res.missingUsers).toEqual([]); - expect(res.missingDevices).toEqual({}); + expect(res[aliUserId][aliDeviceId].device).toBeDefined(); + expect(res[aliUserId][aliDeviceId].sessionId).toBeDefined(); + expect(res[bobUserId]).toEqual({}); expect(bobClient.isRoomEncrypted(roomId)).toBeTruthy(); }); }