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();
});
}