Skip to content

Commit

Permalink
Factor out ensureOlmSessionsForDevices
Browse files Browse the repository at this point in the history
... and move to olmlib
  • Loading branch information
richvdh committed Nov 16, 2016
1 parent 749f53a commit 766e837
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 120 deletions.
129 changes: 9 additions & 120 deletions lib/crypto/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
var unsigned = deviceResult.unsigned || {};

try {
_verifySignature(_olmDevice, deviceResult, userId, deviceId, signKey);
olmlib.verifySignature(_olmDevice, deviceResult, userId, deviceId, signKey);
} catch (e) {
console.log("Unable to verify signature on device " +
userId + ":" + deviceId + ":", e);
Expand Down Expand Up @@ -726,7 +726,8 @@ Crypto.prototype.setRoomEncryption = function(roomId, config) {
*/

/**
* Try to make sure we have established olm sessions for the given users.
* Try to make sure we have established olm sessions for all known devices for
* the given users.
*
* @param {string[]} users list of user ids
*
Expand All @@ -735,19 +736,15 @@ Crypto.prototype.setRoomEncryption = function(roomId, config) {
* {@link module:crypto~OlmSessionResult}
*/
Crypto.prototype.ensureOlmSessionsForUsers = function(users) {
var devicesWithoutSession = [
// [userId, deviceId, deviceInfo], ...
];
var result = {};
var devicesByUser = {};

for (var i = 0; i < users.length; ++i) {
var userId = users[i];
result[userId] = {};
devicesByUser[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) {
Expand All @@ -759,87 +756,13 @@ Crypto.prototype.ensureOlmSessionsForUsers = function(users) {
continue;
}

var sessionId = this._olmDevice.getSessionIdForDevice(key);
if (sessionId === null) {
devicesWithoutSession.push([userId, deviceId, deviceInfo]);
}
result[userId][deviceId] = {
device: deviceInfo,
sessionId: sessionId,
};
devicesByUser[userId].push(deviceInfo);
}
}

if (devicesWithoutSession.length === 0) {
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;
var oneTimeKeyAlgorithm = "signed_curve25519";
return this._baseApis.claimOneTimeKeys(
devicesWithoutSession, oneTimeKeyAlgorithm
).then(function(res) {
for (var i = 0; i < devicesWithoutSession.length; ++i) {
var device = devicesWithoutSession[i];
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 = null;
for (var keyId in deviceRes) {
if (keyId.indexOf(oneTimeKeyAlgorithm + ":") === 0) {
oneTimeKey = deviceRes[keyId];
}
}

if (!oneTimeKey) {
console.warn(
"No one-time keys (alg=" + oneTimeKeyAlgorithm +
") for device " + userId + ":" + deviceId
);
continue;
}

try {
_verifySignature(
self._olmDevice, oneTimeKey, userId, deviceId,
deviceInfo.getFingerprint()
);
} catch (e) {
console.log(
"Unable to verify signature on one-time key for device " +
userId + ":" + deviceId + ":", e
);
continue;
}

var sid;
try {
sid = self._olmDevice.createOutboundSession(
deviceInfo.getIdentityKey(), oneTimeKey.key
);
} catch (e) {
// possibly a bad key
console.error("Error starting session with device " +
userId + ":" + deviceId + ": " + e);
continue;
}

console.log("Started new sessionid " + sid +
" for device " + userId + ":" + deviceId);

result[userId][deviceId].sessionId = sid;
}
return result;
});
return olmlib.ensureOlmSessionsForDevices(
this._olmDevice, this._baseApis, devicesByUser
);
};

/**
Expand Down Expand Up @@ -1212,40 +1135,6 @@ Crypto.prototype._signObject = function(obj) {
obj.signatures = sigs;
};

/**
* Verify the signature on an object
*
* @param {module:crypto/OlmDevice} olmDevice olm wrapper to use for verify op
*
* @param {Object} obj object to check signature on. Note that this will be
* stripped of its 'signatures' and 'unsigned' properties.
*
* @param {string} signingUserId ID of the user whose signature should be checked
*
* @param {string} signingDeviceId ID of the device whose signature should be checked
*
* @param {string} signingKey base64-ed ed25519 public key
*/
function _verifySignature(olmDevice, obj, signingUserId, signingDeviceId, signingKey) {
var signKeyId = "ed25519:" + signingDeviceId;
var signatures = obj.signatures || {};
var userSigs = signatures[signingUserId] || {};
var signature = userSigs[signKeyId];
if (!signature) {
throw Error("No signature");
}

// prepare the canonical json: remove unsigned and signatures, and stringify with
// anotherjson
delete obj.unsigned;
delete obj.signatures;
var json = anotherjson.stringify(obj);

olmDevice.verifySignature(
signingKey, json, signature
);
}

/**
* @see module:crypto/algorithms/base.DecryptionError
*/
Expand Down
166 changes: 166 additions & 0 deletions lib/crypto/olmlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ limitations under the License.
* Utilities common to olm encryption algorithms
*/

var q = require('q');
var anotherjson = require('another-json');

var utils = require("../utils");

/**
Expand Down Expand Up @@ -100,3 +103,166 @@ module.exports.encryptMessageForDevice = function(
deviceKey, sessionId, JSON.stringify(payload)
);
};

/**
* Try to make sure we have established olm sessions for the given devices.
*
* @param {module:crypto/OlmDevice} olmDevice
*
* @param {module:base-apis~MatrixBaseApis} baseApis
*
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices
*
* @return {module:client.Promise} resolves once the sessions are complete, to
* an Object mapping from userId to deviceId to
* {@link module:crypto~OlmSessionResult}
*/
module.exports.ensureOlmSessionsForDevices = function(
olmDevice, baseApis, devicesByUser
) {
var devicesWithoutSession = [
// [userId, deviceId], ...
];
var result = {};

for (var userId in devicesByUser) {
if (!devicesByUser.hasOwnProperty(userId)) { continue; }
result[userId] = {};
var devices = devicesByUser[userId];
for (var j = 0; j < devices.length; j++) {
var deviceInfo = devices[j];
var deviceId = deviceInfo.deviceId;
var key = deviceInfo.getIdentityKey();
var sessionId = olmDevice.getSessionIdForDevice(key);
if (sessionId === null) {
devicesWithoutSession.push([userId, deviceId]);
}
result[userId][deviceId] = {
device: deviceInfo,
sessionId: sessionId,
};
}
}

if (devicesWithoutSession.length === 0) {
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 oneTimeKeyAlgorithm = "signed_curve25519";
return baseApis.claimOneTimeKeys(
devicesWithoutSession, oneTimeKeyAlgorithm
).then(function(res) {
for (var userId in devicesByUser) {
if (!devicesByUser.hasOwnProperty(userId)) { continue; }
var userRes = res.one_time_keys[userId] || {};
var devices = devicesByUser[userId];
for (var j = 0; j < devices.length; j++) {
var deviceInfo = devices[j];
var deviceId = deviceInfo.deviceId;
if (result[userId][deviceId].sessionId) {
// we already have a result for this device
continue;
}

var deviceRes = userRes[deviceId] || {};
var oneTimeKey = null;
for (var keyId in deviceRes) {
if (keyId.indexOf(oneTimeKeyAlgorithm + ":") === 0) {
oneTimeKey = deviceRes[keyId];
}
}

if (!oneTimeKey) {
console.warn(
"No one-time keys (alg=" + oneTimeKeyAlgorithm +
") for device " + userId + ":" + deviceId
);
continue;
}

var sid = _verifyKeyAndStartSession(
olmDevice, oneTimeKey, userId, deviceInfo
);
result[userId][deviceId].sessionId = sid;
}
}
return result;
});
};


function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo) {
var deviceId = deviceInfo.deviceId;
try {
_verifySignature(
olmDevice, oneTimeKey, userId, deviceId,
deviceInfo.getFingerprint()
);
} catch (e) {
console.error(
"Unable to verify signature on one-time key for device " +
userId + ":" + deviceId + ":", e
);
return null;
}

var sid;
try {
sid = olmDevice.createOutboundSession(
deviceInfo.getIdentityKey(), oneTimeKey.key
);
} catch (e) {
// possibly a bad key
console.error("Error starting session with device " +
userId + ":" + deviceId + ": " + e);
return null;
}

console.log("Started new sessionid " + sid +
" for device " + userId + ":" + deviceId);
return sid;
}


/**
* Verify the signature on an object
*
* @param {module:crypto/OlmDevice} olmDevice olm wrapper to use for verify op
*
* @param {Object} obj object to check signature on. Note that this will be
* stripped of its 'signatures' and 'unsigned' properties.
*
* @param {string} signingUserId ID of the user whose signature should be checked
*
* @param {string} signingDeviceId ID of the device whose signature should be checked
*
* @param {string} signingKey base64-ed ed25519 public key
*/
var _verifySignature = module.exports.verifySignature = function(
olmDevice, obj, signingUserId, signingDeviceId, signingKey
) {
var signKeyId = "ed25519:" + signingDeviceId;
var signatures = obj.signatures || {};
var userSigs = signatures[signingUserId] || {};
var signature = userSigs[signKeyId];
if (!signature) {
throw Error("No signature");
}

// prepare the canonical json: remove unsigned and signatures, and stringify with
// anotherjson
delete obj.unsigned;
delete obj.signatures;
var json = anotherjson.stringify(obj);

olmDevice.verifySignature(
signingKey, json, signature
);
};

0 comments on commit 766e837

Please sign in to comment.