1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-28 05:03:59 +03:00

Factor crypto stuff out of MatrixClient

Introduce a new Crypto class which encapsulates all of the the crypto-related
gubbins, replacing it with thin wrappers in MatrixClient.
This commit is contained in:
Richard van der Hoff
2016-08-04 09:03:39 +01:00
parent d9867ba458
commit ad6eec329d
4 changed files with 914 additions and 684 deletions

View File

@@ -24,7 +24,6 @@ var PushProcessor = require('./pushprocessor');
var EventEmitter = require("events").EventEmitter;
var q = require("q");
var url = require('url');
var anotherjson = require('another-json');
var httpApi = require("./http-api");
var MatrixEvent = require("./models/event").MatrixEvent;
@@ -44,20 +43,12 @@ var SCROLLBACK_DELAY_MS = 3000;
var CRYPTO_ENABLED = false;
try {
var OlmDevice = require("./OlmDevice");
var Crypto = require("./crypto");
CRYPTO_ENABLED = true;
} catch (e) {
// Olm not installed.
}
var OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
var DeviceVerification = {
VERIFIED: 1,
UNVERIFIED: 0,
BLOCKED: -1,
};
/**
* Construct a Matrix Client. Only directly construct this if you want to use
* custom modules. Normally, {@link createClient} should be used
@@ -116,7 +107,6 @@ function MatrixClient(opts) {
MatrixBaseApis.call(this, opts);
this.store = opts.store || new StubStore();
this.sessionStore = opts.sessionStore || null;
this.deviceId = opts.deviceId || null;
this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName || "js-sdk device";
@@ -126,38 +116,6 @@ function MatrixClient(opts) {
userId: userId,
};
this._olmDevice = null;
this._cryptoAlgorithms = [];
if (CRYPTO_ENABLED && this.sessionStore !== null && userId !== null &&
this.deviceId !== null) {
this._olmDevice = new OlmDevice(opts.sessionStore);
this._cryptoAlgorithms.push(OLM_ALGORITHM);
// build our device keys: these will later be uploaded
this._deviceKeys = {};
this._deviceKeys["ed25519:" + this.deviceId] =
this._olmDevice.deviceEd25519Key;
this._deviceKeys["curve25519:" + this.deviceId] =
this._olmDevice.deviceCurve25519Key;
// add our own deviceinfo to the sessionstore
var deviceInfo = {
keys: this._deviceKeys,
algorithms: this._cryptoAlgorithms,
verified: DeviceVerification.VERIFIED,
};
var myDevices = this.sessionStore.getEndToEndDevicesForUser(
userId
) || {};
myDevices[opts.deviceId] = deviceInfo;
this.sessionStore.storeEndToEndDevicesForUser(
userId, myDevices
);
setupCryptoEventHandler(this);
}
this.scheduler = opts.scheduler;
if (this.scheduler) {
var self = this;
@@ -192,6 +150,18 @@ function MatrixClient(opts) {
this._txnCtr = 0;
this.timelineSupport = Boolean(opts.timelineSupport);
this.urlPreviewCache = {};
this._crypto = null;
if (CRYPTO_ENABLED && opts.sessionStore !== null &&
userId !== null && this.deviceId !== null) {
this._crypto = new Crypto(
this,
opts.sessionStore,
userId, this.deviceId
);
setupCryptoEventHandler(this);
}
}
utils.inherits(MatrixClient, EventEmitter);
utils.extend(MatrixClient.prototype, MatrixBaseApis.prototype);
@@ -291,7 +261,7 @@ MatrixClient.prototype.retryImmediately = function() {
* @return {boolean} True if end-to-end is enabled.
*/
MatrixClient.prototype.isCryptoEnabled = function() {
return CRYPTO_ENABLED && this.sessionStore !== null;
return this._crypto !== null;
};
@@ -302,105 +272,26 @@ MatrixClient.prototype.isCryptoEnabled = function() {
* disabled.
*/
MatrixClient.prototype.getDeviceEd25519Key = function() {
if (!this._olmDevice) {
if (!this._crypto) {
return null;
}
return this._olmDevice.deviceEd25519Key;
return this._crypto.getDeviceEd25519Key();
};
/**
* Upload the device keys to the homeserver and ensure that the
* homeserver has enough one-time keys.
* @param {number} maxKeys The maximum number of keys to generate
* @param {object} deferred A deferred to resolve when the keys are uploaded.
* @return {object} A promise that will resolve when the keys are uploaded.
*/
MatrixClient.prototype.uploadKeys = function(maxKeys, deferred) {
var self = this;
return _uploadDeviceKeys(this).then(function(res) {
var keyCount = res.one_time_key_counts.curve25519 || 0;
var maxOneTimeKeys = self._olmDevice.maxNumberOfOneTimeKeys();
var keyLimit = Math.floor(maxOneTimeKeys / 2);
var numberToGenerate = Math.max(keyLimit - keyCount, 0);
if (maxKeys !== undefined) {
numberToGenerate = Math.min(numberToGenerate, maxKeys);
}
MatrixClient.prototype.uploadKeys = function(maxKeys) {
if (this._crypto === null) {
throw new Error("End-to-end encryption disabled");
}
if (numberToGenerate <= 0) {
return;
}
self._olmDevice.generateOneTimeKeys(numberToGenerate);
return _uploadOneTimeKeys(self);
});
return this._crypto.uploadKeys(maxKeys);
};
// returns a promise which resolves to the response
function _uploadDeviceKeys(client) {
if (!client._olmDevice) {
return q.reject(new Error("End-to-end encryption disabled"));
}
var userId = client.credentials.userId;
var deviceId = client.deviceId;
var deviceKeys = {
algorithms: client._cryptoAlgorithms,
device_id: deviceId,
keys: client._deviceKeys,
user_id: userId,
};
var sig = client._olmDevice.sign(anotherjson.stringify(deviceKeys));
deviceKeys.signatures = {};
deviceKeys.signatures[userId] = {};
deviceKeys.signatures[userId]["ed25519:" + deviceId] = sig;
return client.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,
});
}
// returns a promise which resolves to the response
function _uploadOneTimeKeys(client) {
if (!client._olmDevice) {
return q.reject(new Error("End-to-end encryption disabled"));
}
var oneTimeKeys = client._olmDevice.getOneTimeKeys();
var oneTimeJson = {};
for (var keyId in oneTimeKeys.curve25519) {
if (oneTimeKeys.curve25519.hasOwnProperty(keyId)) {
oneTimeJson["curve25519:" + keyId] = oneTimeKeys.curve25519[keyId];
}
}
return client.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: client.deviceId,
}).then(function(res) {
client._olmDevice.markKeysAsPublished();
return res;
});
}
/**
* Stored information about a user's device
* @typedef {Object} DeviceInfo
* @property {string[]} list of algorithms supported by this device
* @property {Object} keys a map from &lt;key type&gt;:&lt;id&gt; -> key
* @property {DeviceVerification} whether the device has been verified by the user
*/
/**
* Download the keys for a list of users and stores the keys in the session
* store.
@@ -408,126 +299,15 @@ function _uploadOneTimeKeys(client) {
* @param {bool} forceDownload Always download the keys even if cached.
*
* @return {Promise} A promise which resolves to a map userId->deviceId->{@link
* module:client~DeviceInfo|DeviceInfo}.
* module:crypto~DeviceInfo|DeviceInfo}.
*/
MatrixClient.prototype.downloadKeys = function(userIds, forceDownload) {
if (this.sessionStore === null) {
if (this._crypto === null) {
return q.reject(new Error("End-to-end encryption disabled"));
}
var stored = {};
var downloadQuery = {};
var downloadKeys = false;
for (var i = 0; i < userIds.length; ++i) {
var userId = userIds[i];
var devices = this.sessionStore.getEndToEndDevicesForUser(userId);
stored[userId] = devices || {};
if (devices && !forceDownload) {
continue;
}
downloadKeys = true;
downloadQuery[userId] = {};
}
if (!downloadKeys) {
return q(stored);
}
var path = "/keys/query";
var content = {device_keys: downloadQuery};
var self = this;
return this._http.authedRequestWithPrefix(
undefined, "POST", path, undefined, content,
httpApi.PREFIX_UNSTABLE
).then(function(res) {
for (var userId in res.device_keys) {
if (!downloadQuery.hasOwnProperty(userId)) {
continue;
}
var userStore = stored[userId];
var updated = _updateStoredDeviceKeysForUser(
userId, userStore, res.device_keys[userId]
);
if (updated) {
self.sessionStore.storeEndToEndDevicesForUser(
userId, userStore
);
}
}
return stored;
});
return this._crypto.downloadKeys(userIds, forceDownload);
};
function _updateStoredDeviceKeysForUser(userId, userStore, userResult) {
var updated = false;
// remove any devices in the store which aren't in the response
for (var deviceId in userStore) {
if (!userStore.hasOwnProperty(deviceId)) {
continue;
}
if (!(deviceId in userResult)) {
console.log("Device " + userId + ":" + deviceId +
" has been removed");
delete userStore[deviceId];
updated = true;
}
}
for (deviceId in userResult) {
if (!userResult.hasOwnProperty(deviceId)) {
continue;
}
var deviceRes = userResult[deviceId];
var deviceStore;
if (!deviceRes.keys) {
// no keys?
continue;
}
var signKey = deviceRes.keys["ed25519:" + deviceId];
if (!signKey) {
console.log("Device " + userId + ": " +
deviceId + " has no ed25519 key");
continue;
}
if (deviceId in userStore) {
// already have this device.
deviceStore = userStore[deviceId];
if (deviceStore.keys["ed25519:" + deviceId] != signKey) {
// this should only happen if the list has been MITMed; we are
// best off sticking with the original keys.
//
// Should we warn the user about it somehow?
console.warn("Ed25519 key for device" + userId + ": " +
deviceId + " has changed");
continue;
}
} else {
userStore[deviceId] = deviceStore = {
verified: DeviceVerification.UNVERIFIED
};
}
// TODO: check signature. Remember that we need to check for
// _olmDevice.
deviceStore.keys = deviceRes.keys;
deviceStore.algorithms = deviceRes.algorithms;
deviceStore.unsigned = deviceRes.unsigned;
updated = true;
}
return updated;
}
/**
* List the stored device keys for a user id
*
@@ -537,37 +317,10 @@ function _updateStoredDeviceKeysForUser(userId, userStore, userResult) {
* "key", and "display_name" parameters.
*/
MatrixClient.prototype.listDeviceKeys = function(userId) {
if (!this.sessionStore) {
return [];
if (this._crypto === null) {
throw new Error("End-to-end encryption disabled");
}
var devices = this.sessionStore.getEndToEndDevicesForUser(userId);
var result = [];
if (devices) {
var deviceId;
var deviceIds = [];
for (deviceId in devices) {
if (devices.hasOwnProperty(deviceId)) {
deviceIds.push(deviceId);
}
}
deviceIds.sort();
for (var i = 0; i < deviceIds.length; ++i) {
deviceId = deviceIds[i];
var device = devices[deviceId];
var ed25519Key = device.keys["ed25519:" + deviceId];
var unsigned = device.unsigned || {};
if (ed25519Key) {
result.push({
id: deviceId,
key: ed25519Key,
verified: Boolean(device.verified == DeviceVerification.VERIFIED),
blocked: Boolean(device.verified == DeviceVerification.BLOCKED),
display_name: unsigned.device_display_name,
});
}
}
}
return result;
return this._crypto.listDeviceKeys(userId);
};
/**
@@ -608,40 +361,13 @@ MatrixClient.prototype.setDeviceBlocked = function(userId, deviceId, blocked) {
};
function _setDeviceVerification(client, userId, deviceId, verified, blocked) {
if (!client.sessionStore) {
if (!client._crypto) {
throw new Error("End-to-End encryption disabled");
}
var devices = client.sessionStore.getEndToEndDevicesForUser(userId);
if (!devices || !devices[deviceId]) {
throw new Error("Unknown device " + userId + ":" + deviceId);
}
var dev = devices[deviceId];
var verificationStatus = dev.verified;
if (verified) {
verificationStatus = DeviceVerification.VERIFIED;
} else if (verified !== null && verificationStatus == DeviceVerification.VERIFIED) {
verificationStatus = DeviceVerification.UNVERIFIED;
}
if (blocked) {
verificationStatus = DeviceVerification.BLOCKED;
} else if (blocked !== null && verificationStatus == DeviceVerification.BLOCKED) {
verificationStatus = DeviceVerification.UNVERIFIED;
}
if (dev.verified === verificationStatus) {
return;
}
dev.verified = verificationStatus;
client.sessionStore.storeEndToEndDevicesForUser(userId, devices);
client.emit("deviceVerificationChanged", userId, deviceId, dev);
client._crypto.setDeviceVerification(userId, deviceId, verified, blocked);
client.emit("deviceVerificationChanged", userId, deviceId);
}
/**
* Check if the sender of an event is verified
*
@@ -651,50 +377,22 @@ function _setDeviceVerification(client, userId, deviceId, verified, blocked) {
* {@link module:client~MatrixClient#setDeviceVerified|setDeviceVerified}.
*/
MatrixClient.prototype.isEventSenderVerified = function(event) {
if (!this.sessionStore) {
if (!this._crypto) {
return false;
}
var cryptoContent = event.getWireContent();
var algorithm = cryptoContent.algorithm;
if (algorithm !== OLM_ALGORITHM) {
console.warn("unable to verify event with algorithm " + algorithm);
return false;
}
var devices = this.sessionStore.getEndToEndDevicesForUser(event.getSender());
if (!devices) {
return false;
}
var sender_key = cryptoContent.sender_key;
if (!sender_key) {
return false;
}
for (var deviceId in devices) {
if (!devices.hasOwnProperty(deviceId)) {
continue;
}
var algorithm = cryptoContent.algorithm;
var device = devices[deviceId];
for (var keyId in device.keys) {
if (!device.keys.hasOwnProperty(keyId)) {
continue;
}
if (keyId.indexOf("curve25519:") !== 0) {
continue;
}
var deviceKey = device.keys[keyId];
if (deviceKey == sender_key) {
return device.verified == DeviceVerification.VERIFIED;
}
}
}
// doesn't match a known device
return false;
return this._crypto.isSenderKeyVerified(
event.getSender(), algorithm, sender_key
);
};
/**
@@ -714,24 +412,13 @@ function setupCryptoEventHandler(client) {
function onCryptoEvent(client, event) {
var roomId = event.getRoomId();
// if we already have encryption in this room, we should ignore this event
// (for now at least. maybe we should alert the user somehow?)
var content = event.getContent();
var existingConfig = client.sessionStore.getEndToEndRoom(roomId);
if (existingConfig) {
if (JSON.stringify(existingConfig) != JSON.stringify(content)) {
console.error("Ignoring m.room.encryption event which requests " +
"a change of config in " + roomId);
return;
}
}
try {
client.setRoomEncryption(roomId, content).done();
} catch (e) {
console.error("Error configuring encryption in room " + roomId +
": " + e);
":", e);
}
}
@@ -742,133 +429,21 @@ function onCryptoEvent(client, event) {
* @return {Object} A promise that will resolve when encryption is setup.
*/
MatrixClient.prototype.setRoomEncryption = function(roomId, config) {
if (!this._olmDevice) {
if (!this._crypto) {
throw new Error("End-to-End encryption disabled");
}
var self = this;
if (config.algorithm === OLM_ALGORITHM) {
// remove spurious keys
config = {
algorithm: OLM_ALGORITHM,
};
this.sessionStore.storeEndToEndRoom(roomId, config);
var room = this.getRoom(roomId);
if (!room) {
console.warn("Enabling encryption in unknown room " + roomId);
return q({});
}
var users = utils.map(room.getJoinedMembers(), function(u) {
return u.userId;
});
return self.downloadKeys(users, true).then(function(res) {
return self._ensureOlmSessionsForUsers(users);
});
var roomMembers = [];
var room = this.getRoom(roomId);
if (!room) {
console.warn("Enabling encryption in unknown room " + roomId);
} else {
throw new Error("Unknown algorithm: " + config.algorithm);
}
};
/**
* 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 <tt>missingUsers</tt> (a list of users with no known
* olm devices), and <tt>missingDevices</tt> a list of olm devices with no
* known one-time keys.
*
* @private
*/
MatrixClient.prototype._ensureOlmSessionsForUsers = function(users) {
var devicesWithoutSession = [];
var userWithoutDevices = [];
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]);
}
}
}
}
roomMembers = utils.map(room.getJoinedMembers(), function(u) {
return u.userId;
});
}
if (devicesWithoutSession.length === 0) {
return q({
missingUsers: userWithoutDevices,
missingDevices: []
});
}
var queries = {};
for (i = 0; i < devicesWithoutSession.length; ++i) {
var device = devicesWithoutSession[i];
var query = queries[device[0]] || {};
queries[device[0]] = query;
query[device[1]] = "curve25519";
}
var path = "/keys/claim";
var content = {one_time_keys: queries};
var self = this;
return this._http.authedRequestWithPrefix(
undefined, "POST", path, undefined, content,
httpApi.PREFIX_UNSTABLE
).then(function(res) {
var missing = {};
for (i = 0; i < devicesWithoutSession.length; ++i) {
var device = devicesWithoutSession[i];
var userRes = res.one_time_keys[device[0]] || {};
var deviceRes = userRes[device[1]];
var oneTimeKey;
for (var keyId in deviceRes) {
if (keyId.indexOf("curve25519:") === 0) {
oneTimeKey = deviceRes[keyId];
}
}
if (oneTimeKey) {
var sid = self._olmDevice.createOutboundSession(
device[2], oneTimeKey
);
console.log("Started new sessionid " + sid +
" for device " + device[2]);
} else {
missing[device[0]] = missing[device[0]] || [];
missing[device[0]].push([device[1]]);
}
}
return {
missingUsers: userWithoutDevices,
missingDevices: missing
};
});
};
/**
* Disable encryption for a room.
* @param {string} roomId the room to disable encryption for.
*/
MatrixClient.prototype.disableRoomEncryption = function(roomId) {
if (this.sessionStore !== null) {
this.sessionStore.storeEndToEndRoom(roomId, null);
}
return this._crypto.setRoomEncryption(roomId, config, roomMembers);
};
/**
@@ -877,13 +452,49 @@ MatrixClient.prototype.disableRoomEncryption = function(roomId) {
* @return {bool} whether encryption is enabled.
*/
MatrixClient.prototype.isRoomEncrypted = function(roomId) {
if (CRYPTO_ENABLED && this.sessionStore !== null) {
return (this.sessionStore.getEndToEndRoom(roomId) && true) || false;
} else {
if (!this._crypto) {
return false;
}
return this._crypto.isRoomEncrypted(roomId);
};
/**
* Decrypt a received event according to the algorithm specified in the event.
*
* @param {MatrixClient} client
* @param {object} raw event
*
* @return {object} decrypted payload (with properties 'type', 'content')
*/
function _decryptMessage(client, event) {
if (!client._crypto) {
return _badEncryptedMessage(event, "**Encryption not enabled**");
}
try {
return client._crypto.decryptEvent(event);
} catch (e) {
if (!(e instanceof Crypto.DecryptionError)) {
throw e;
}
return _badEncryptedMessage(event, "**" + e.message + "**");
}
}
function _badEncryptedMessage(event, reason) {
return {
type: "m.room.message",
content: {
msgtype: "m.bad.encrypted",
body: reason,
content: event.content,
},
};
}
// Room ops
// ========
/**
* Get the room for the given room ID.
@@ -1206,211 +817,6 @@ MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
};
/**
* Encrypt an event according to the configuration of the room, if necessary.
*
* @param {MatrixClient} client
* @param {module:models/event.MatrixEvent} event event to be sent
*
* @private
*/
function _encryptEventIfNeeded(client, event) {
if (event.isEncrypted()) {
// this event has already been encrypted; this happens if the
// encryption step succeeded, but the send step failed on the first
// attempt.
return;
}
if (event.getType() !== "m.room.message") {
// we only encrypt m.room.message
return;
}
if (!client.sessionStore) {
// End to end encryption isn't enabled if we don't have a session
// store.
return;
}
var roomId = event.getRoomId();
var e2eRoomInfo = client.sessionStore.getEndToEndRoom(roomId);
if (!e2eRoomInfo || !e2eRoomInfo.algorithm) {
// not encrypting messages in this room
return;
}
var encryptedContent = _encryptMessage(
client, roomId, e2eRoomInfo, event.getType(), event.getContent()
);
event.makeEncrypted("m.room.encrypted", encryptedContent);
}
function _encryptMessage(client, roomId, e2eRoomInfo, eventType, content) {
if (!client.sessionStore) {
throw new Error(
"Client must have an end-to-end session store to encrypt messages"
);
}
if (e2eRoomInfo.algorithm === OLM_ALGORITHM) {
var room = client.getRoom(roomId);
if (!room) {
throw new Error("Cannot send encrypted messages in unknown rooms");
}
// pick the list of recipients based on the membership list.
//
// TODO: there is a race condition here! What if a new user turns up
// just as you are sending a secret message?
var users = utils.map(room.getJoinedMembers(), function(u) {
return u.userId;
});
var participantKeys = [];
for (var i = 0; i < users.length; ++i) {
var userId = users[i];
var devices = client.sessionStore.getEndToEndDevicesForUser(userId);
for (var deviceId in devices) {
if (devices.hasOwnProperty(deviceId)) {
var dev = devices[deviceId];
if (dev.verified === DeviceVerification.BLOCKED) {
continue;
}
for (var keyId in dev.keys) {
if (keyId.indexOf("curve25519:") === 0) {
participantKeys.push(dev.keys[keyId]);
}
}
}
}
}
participantKeys.sort();
var participantHash = ""; // Olm.sha256(participantKeys.join());
var payloadJson = {
room_id: roomId,
type: eventType,
fingerprint: participantHash,
sender_device: client.deviceId,
content: content
};
var ciphertext = {};
var payloadString = JSON.stringify(payloadJson);
for (i = 0; i < participantKeys.length; ++i) {
var deviceKey = participantKeys[i];
if (deviceKey == client._olmDevice.deviceCurve25519Key) {
continue;
}
var sessionIds = client._olmDevice.getSessionIdsForDevice(deviceKey);
// Use the session with the lowest ID.
sessionIds.sort();
if (sessionIds.length === 0) {
// If we don't have a session for a device then
// we can't encrypt a message for it.
continue;
}
var sessionId = sessionIds[0];
console.log("Using sessionid " + sessionId + " for device " + deviceKey);
ciphertext[deviceKey] = client._olmDevice.encryptMessage(
deviceKey, sessionId, payloadString
);
}
var encryptedContent = {
algorithm: e2eRoomInfo.algorithm,
sender_key: client._olmDevice.deviceCurve25519Key,
ciphertext: ciphertext
};
return encryptedContent;
} else {
throw new Error("Unknown end-to-end algorithm: " + e2eRoomInfo.algorithm);
}
}
/**
* Decrypt a received event according to the algorithm specified in the event.
*
* @param {MatrixClient} client
* @param {object} raw event
*
* @return {object} decrypted payload (with properties 'type', 'content')
*/
function _decryptMessage(client, event) {
if (client.sessionStore === null || !CRYPTO_ENABLED) {
// End to end encryption isn't enabled if we don't have a session
// store.
return _badEncryptedMessage(event, "**Encryption not enabled**");
}
var content = event.content;
if (content.algorithm === OLM_ALGORITHM) {
var deviceKey = content.sender_key;
var ciphertext = content.ciphertext;
if (!ciphertext) {
return _badEncryptedMessage(event, "**Missing ciphertext**");
}
if (!(client._olmDevice.deviceCurve25519Key in content.ciphertext)) {
return _badEncryptedMessage(event, "**Not included in recipients**");
}
var message = content.ciphertext[client._olmDevice.deviceCurve25519Key];
var sessionIds = client._olmDevice.getSessionIdsForDevice(deviceKey);
var payloadString = null;
var foundSession = false;
for (var i = 0; i < sessionIds.length; i++) {
var sessionId = sessionIds[i];
var res = client._olmDevice.decryptMessage(
deviceKey, sessionId, message.type, message.body
);
payloadString = res.payload;
if (payloadString) {
console.log("decrypted with sessionId " + sessionId);
break;
}
if (res.matchesInbound) {
// this was a prekey message which matched this session; don't
// create a new session.
foundSession = true;
break;
}
}
if (message.type === 0 && !foundSession && payloadString === null) {
try {
payloadString = client._olmDevice.createInboundSession(
deviceKey, message.type, message.body
);
console.log("created new inbound sesion");
} catch (e) {
// Failed to decrypt with a new session.
}
}
// TODO: Check the sender user id matches the sender key.
if (payloadString !== null) {
return JSON.parse(payloadString);
} else {
return _badEncryptedMessage(event, "**Bad Encrypted Message**");
}
}
return _badEncryptedMessage(event, "**Unknown algorithm**");
}
function _badEncryptedMessage(event, reason) {
return {
type: "m.room.message",
content: {
msgtype: "m.bad.encrypted",
body: reason,
content: event.content,
},
};
}
// encrypts the event if necessary
// adds the event to the queue, or sends it
// marks the event as sent/unsent
@@ -1420,7 +826,9 @@ function _sendEvent(client, room, event, callback) {
// so that we can handle synchronous and asynchronous exceptions with the
// same code path.
return q().then(function() {
_encryptEventIfNeeded(client, event);
if (client._crypto) {
client._crypto.encryptEventIfNeeded(event, room);
}
var promise;
// this event may be queued
@@ -3062,8 +2470,8 @@ MatrixClient.prototype.startClient = function(opts) {
this._clientOpts = opts;
if (this._olmDevice) {
this.uploadKeys(5).done();
if (this._crypto) {
this._crypto.uploadKeys(5).done();
}
// periodically poll for turn servers if we support voip
@@ -3514,7 +2922,7 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
*
* @event module:client~MatrixClient#"deviceVerificationChanged"
* @param {string} userId the owner of the verified device
* @param {module:client~DeviceInfo} device information about the verified device
* @param {string} deviceId the id of the verified device
*/
/**