You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-28 05:03:59 +03:00
Factor Olm encryption/decryption out to new classes
- to make way for alternative encryption algorithms. We now store an encryption object for each room, rather than referring to sessionstore on each event. Also a little light tidying to the jsdocs.
This commit is contained in:
@@ -15,6 +15,12 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* olm.js wrapper
|
||||||
|
*
|
||||||
|
* @module OlmDevice
|
||||||
|
*/
|
||||||
|
|
||||||
var Olm = require("olm");
|
var Olm = require("olm");
|
||||||
var utils = require("./utils");
|
var utils = require("./utils");
|
||||||
|
|
||||||
@@ -25,6 +31,8 @@ var utils = require("./utils");
|
|||||||
* Accounts and sessions are kept pickled in a sessionStore.
|
* Accounts and sessions are kept pickled in a sessionStore.
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
|
* @alias module:OlmDevice
|
||||||
|
*
|
||||||
* @param {Object} sessionStore A store to be used for data in end-to-end
|
* @param {Object} sessionStore A store to be used for data in end-to-end
|
||||||
* crypto
|
* crypto
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2315,7 +2315,7 @@ MatrixClient.prototype.getTurnServers = function() {
|
|||||||
* appear in a room's timeline. If "<b>chronological</b>", messages will appear
|
* appear in a room's timeline. If "<b>chronological</b>", messages will appear
|
||||||
* in the timeline when the call to <code>sendEvent</code> was made. If
|
* in the timeline when the call to <code>sendEvent</code> was made. If
|
||||||
* "<b>detached</b>", pending messages will appear in a separate list,
|
* "<b>detached</b>", pending messages will appear in a separate list,
|
||||||
* accessbile via {@link module:models/room~Room#getPendingEvents}. Default:
|
* accessbile via {@link module:models/room#getPendingEvents}. Default:
|
||||||
* "chronological".
|
* "chronological".
|
||||||
*
|
*
|
||||||
* @param {Number=} opts.pollTimeout The number of milliseconds to wait on /events.
|
* @param {Number=} opts.pollTimeout The number of milliseconds to wait on /events.
|
||||||
|
|||||||
137
lib/crypto-algorithms/base.js
Normal file
137
lib/crypto-algorithms/base.js
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal module. Defines the base classes of the encryption implementations
|
||||||
|
*
|
||||||
|
* @module crypto-algorithms/base
|
||||||
|
*/
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map of registered encryption algorithm classes. A map from string to {@link
|
||||||
|
* module:crypto-algorithms/base.EncryptionAlgorithm|EncryptionAlgorithm} class
|
||||||
|
*
|
||||||
|
* @type {Object.<string, function(new: module:crypto-algorithms/base.EncryptionAlgorithm)>}
|
||||||
|
*/
|
||||||
|
module.exports.ENCRYPTION_CLASSES = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map of registered encryption algorithm classes. Map from string to {@link
|
||||||
|
* module:crypto-algorithms/base.DecryptionAlgorithm|DecryptionAlgorithm} class
|
||||||
|
*
|
||||||
|
* @type {Object.<string, function(new: module:crypto-algorithms/base.DecryptionAlgorithm)>}
|
||||||
|
*/
|
||||||
|
module.exports.DECRYPTION_CLASSES = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base type for encryption implementations
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*
|
||||||
|
* @param {string} deviceId The identifier for this device.
|
||||||
|
* @param {module:crypto} crypto crypto core
|
||||||
|
* @param {module:OlmDevice} olmDevice olm.js wrapper
|
||||||
|
*/
|
||||||
|
module.exports.EncryptionAlgorithm = function(deviceId, crypto, olmDevice) {
|
||||||
|
this._deviceId = deviceId;
|
||||||
|
this._crypto = crypto;
|
||||||
|
this._olmDevice = olmDevice;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise this EncryptionAlgorithm instance for a particular room
|
||||||
|
*
|
||||||
|
* @method module:crypto-algorithms/base.EncryptionAlgorithm#initRoomEncryption
|
||||||
|
* @abstract
|
||||||
|
*
|
||||||
|
* @param {string[]} roomMembers list of currently-joined users in the room
|
||||||
|
* @return {module:client.Promise} Promise which resolves when setup is complete
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt a message event
|
||||||
|
*
|
||||||
|
* @method module:crypto-algorithms/base.EncryptionAlgorithm#encryptMessage
|
||||||
|
* @abstract
|
||||||
|
*
|
||||||
|
* @param {module:models/room} room
|
||||||
|
* @param {string} eventType
|
||||||
|
* @param {object} plaintext event content
|
||||||
|
*
|
||||||
|
* @return {object} new event body
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base type for decryption implementations
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {string} deviceId The identifier for this device.
|
||||||
|
* @param {module:crypto} crypto crypto core
|
||||||
|
* @param {module:OlmDevice} olmDevice olm.js wrapper
|
||||||
|
*/
|
||||||
|
module.exports.DecryptionAlgorithm = function(deviceId, crypto, olmDevice) {
|
||||||
|
this._deviceId = deviceId;
|
||||||
|
this._crypto = crypto;
|
||||||
|
this._olmDevice = olmDevice;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt an event
|
||||||
|
*
|
||||||
|
* @method module:crypto-algorithms/base.DecryptionAlgorithm#decryptEvent
|
||||||
|
* @abstract
|
||||||
|
*
|
||||||
|
* @param {object} event raw event
|
||||||
|
*
|
||||||
|
* @return {object} decrypted payload (with properties 'type', 'content')
|
||||||
|
*
|
||||||
|
* @throws {module:crypto-algorithms/base.DecryptionError} if there is a
|
||||||
|
* problem decrypting the event
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when decryption fails
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {string} msg message describing the problem
|
||||||
|
* @extends Error
|
||||||
|
*/
|
||||||
|
module.exports.DecryptionError = function(msg) {
|
||||||
|
this.message = msg;
|
||||||
|
};
|
||||||
|
utils.inherits(module.exports.DecryptionError, Error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an encryption/decryption class for a particular algorithm
|
||||||
|
*
|
||||||
|
* @param {string} algorithm algorithm tag to register for
|
||||||
|
*
|
||||||
|
* @param {class} encryptor {@link
|
||||||
|
* module:crypto-algorithms/base.EncryptionAlgorithm|EncryptionAlgorithm}
|
||||||
|
* implementation
|
||||||
|
*
|
||||||
|
* @param {class} decryptor {@link
|
||||||
|
* module:crypto-algorithms/base.DecryptionAlgorithm|DecryptionAlgorithm}
|
||||||
|
* implementation
|
||||||
|
*/
|
||||||
|
module.exports.registerAlgorithm = function(algorithm, encryptor, decryptor) {
|
||||||
|
module.exports.ENCRYPTION_CLASSES[algorithm] = encryptor;
|
||||||
|
module.exports.DECRYPTION_CLASSES[algorithm] = decryptor;
|
||||||
|
};
|
||||||
39
lib/crypto-algorithms/index.js
Normal file
39
lib/crypto-algorithms/index.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module crypto-algorithms
|
||||||
|
*/
|
||||||
|
|
||||||
|
var base = require("./base");
|
||||||
|
|
||||||
|
require("./olm");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see module:crypto-algorithms/base.ENCRYPTION_CLASSES
|
||||||
|
*/
|
||||||
|
module.exports.ENCRYPTION_CLASSES = base.ENCRYPTION_CLASSES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see module:crypto-algorithms/base.DECRYPTION_CLASSES
|
||||||
|
*/
|
||||||
|
module.exports.DECRYPTION_CLASSES = base.DECRYPTION_CLASSES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see module:crypto-algorithms/base.DecryptionError
|
||||||
|
*/
|
||||||
|
module.exports.DecryptionError = base.DecryptionError;
|
||||||
218
lib/crypto-algorithms/olm.js
Normal file
218
lib/crypto-algorithms/olm.js
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines m.olm encryption/decryption
|
||||||
|
*
|
||||||
|
* @module crypto-algorithms/olm
|
||||||
|
*/
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
|
||||||
|
var base = require("./base");
|
||||||
|
|
||||||
|
var OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Olm encryption implementation
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @extends {module:crypto-algorithms/base.EncryptionAlgorithm}
|
||||||
|
*
|
||||||
|
* @param {string} deviceId The identifier for this device.
|
||||||
|
* @param {module:crypto} crypto crypto core
|
||||||
|
* @param {module:OlmDevice} olmDevice olm.js wrapper
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function OlmEncryption(deviceId, crypto, olmDevice) {
|
||||||
|
base.EncryptionAlgorithm.call(this, deviceId, crypto, olmDevice);
|
||||||
|
}
|
||||||
|
utils.inherits(OlmEncryption, base.EncryptionAlgorithm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
* @param {string[]} roomMembers list of currently-joined users in the room
|
||||||
|
* @return {module:client.Promise} Promise which resolves when setup is complete
|
||||||
|
*/
|
||||||
|
OlmEncryption.prototype.initRoomEncryption = function(roomMembers) {
|
||||||
|
var crypto = this._crypto;
|
||||||
|
return crypto.downloadKeys(roomMembers, true).then(function(res) {
|
||||||
|
return crypto.ensureOlmSessionsForUsers(roomMembers);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*
|
||||||
|
* @param {module:models/room} room
|
||||||
|
* @param {string} eventType
|
||||||
|
* @param {object} plaintext event content
|
||||||
|
*
|
||||||
|
* @return {object} new event body
|
||||||
|
*/
|
||||||
|
OlmEncryption.prototype.encryptMessage = function(room, eventType, content) {
|
||||||
|
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 = this._crypto.getStoredDevicesForUser(userId);
|
||||||
|
for (var j = 0; j < devices.length; ++j) {
|
||||||
|
var dev = devices[j];
|
||||||
|
if (dev.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: room.roomId,
|
||||||
|
type: eventType,
|
||||||
|
fingerprint: participantHash,
|
||||||
|
sender_device: this._deviceId,
|
||||||
|
content: content
|
||||||
|
};
|
||||||
|
var ciphertext = {};
|
||||||
|
var payloadString = JSON.stringify(payloadJson);
|
||||||
|
for (i = 0; i < participantKeys.length; ++i) {
|
||||||
|
var deviceKey = participantKeys[i];
|
||||||
|
if (deviceKey == this._olmDevice.deviceCurve25519Key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var sessionIds = this._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] = this._olmDevice.encryptMessage(
|
||||||
|
deviceKey, sessionId, payloadString
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var encryptedContent = {
|
||||||
|
algorithm: OLM_ALGORITHM,
|
||||||
|
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||||
|
ciphertext: ciphertext
|
||||||
|
};
|
||||||
|
return encryptedContent;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Olm decryption implementation
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @extends {module:crypto-algorithms/base.DecryptionAlgorithm}
|
||||||
|
*
|
||||||
|
* @param {string} deviceId The identifier for this device.
|
||||||
|
* @param {module:crypto} crypto crypto core
|
||||||
|
* @param {module:OlmDevice} olmDevice olm.js wrapper
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function OlmDecryption(deviceId, crypto, olmDevice) {
|
||||||
|
base.DecryptionAlgorithm.call(this, deviceId, crypto, olmDevice);
|
||||||
|
}
|
||||||
|
utils.inherits(OlmDecryption, base.DecryptionAlgorithm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*
|
||||||
|
* @param {object} event raw event
|
||||||
|
*
|
||||||
|
* @return {object} decrypted payload (with properties 'type', 'content')
|
||||||
|
*
|
||||||
|
* @throws {module:crypto-algorithms/base.DecryptionError} if there is a
|
||||||
|
* problem decrypting the event
|
||||||
|
*/
|
||||||
|
OlmDecryption.prototype.decryptEvent = function(event) {
|
||||||
|
var content = event.content;
|
||||||
|
var deviceKey = content.sender_key;
|
||||||
|
var ciphertext = content.ciphertext;
|
||||||
|
|
||||||
|
if (!ciphertext) {
|
||||||
|
throw new base.DecryptionError("Missing ciphertext");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(this._olmDevice.deviceCurve25519Key in content.ciphertext)) {
|
||||||
|
throw new base.DecryptionError("Not included in recipients");
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = content.ciphertext[this._olmDevice.deviceCurve25519Key];
|
||||||
|
var sessionIds = this._olmDevice.getSessionIdsForDevice(deviceKey);
|
||||||
|
var payloadString = null;
|
||||||
|
var foundSession = false;
|
||||||
|
for (var i = 0; i < sessionIds.length; i++) {
|
||||||
|
var sessionId = sessionIds[i];
|
||||||
|
var res = this._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 = this._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.
|
||||||
|
// TODO: check the room_id and fingerprint
|
||||||
|
if (payloadString !== null) {
|
||||||
|
return JSON.parse(payloadString);
|
||||||
|
} else {
|
||||||
|
throw new base.DecryptionError("Bad Encrypted Message");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
base.registerAlgorithm(OLM_ALGORITHM, OlmEncryption, OlmDecryption);
|
||||||
223
lib/crypto.js
223
lib/crypto.js
@@ -23,11 +23,15 @@ limitations under the License.
|
|||||||
var anotherjson = require('another-json');
|
var anotherjson = require('another-json');
|
||||||
var q = require("q");
|
var q = require("q");
|
||||||
|
|
||||||
var utils = require("./utils");
|
|
||||||
var OlmDevice = require("./OlmDevice");
|
var OlmDevice = require("./OlmDevice");
|
||||||
|
|
||||||
|
var algorithms = require("./crypto-algorithms");
|
||||||
|
|
||||||
var OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
|
var OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum
|
||||||
|
*/
|
||||||
var DeviceVerification = {
|
var DeviceVerification = {
|
||||||
VERIFIED: 1,
|
VERIFIED: 1,
|
||||||
UNVERIFIED: 0,
|
UNVERIFIED: 0,
|
||||||
@@ -46,7 +50,7 @@ var DeviceVerification = {
|
|||||||
* @property {Object.<string,string>} keys a map from
|
* @property {Object.<string,string>} keys a map from
|
||||||
* <key type>:<id> -> <base64-encoded key>>
|
* <key type>:<id> -> <base64-encoded key>>
|
||||||
*
|
*
|
||||||
* @property {DeviceVerification} verified whether the device has been
|
* @property {module:crypto~DeviceVerification} verified whether the device has been
|
||||||
* verified by the user
|
* verified by the user
|
||||||
*
|
*
|
||||||
* @property {Object} unsigned additional data from the homeserver
|
* @property {Object} unsigned additional data from the homeserver
|
||||||
@@ -119,6 +123,7 @@ DeviceInfo.prototype.getDisplayname = function() {
|
|||||||
/**
|
/**
|
||||||
* Cryptography bits
|
* Cryptography bits
|
||||||
*
|
*
|
||||||
|
* @alias module:crypto.Crypto
|
||||||
* @constructor
|
* @constructor
|
||||||
* @alias module:crypto
|
* @alias module:crypto
|
||||||
*
|
*
|
||||||
@@ -137,10 +142,10 @@ function Crypto(baseApis, sessionStore, userId, deviceId) {
|
|||||||
this._userId = userId;
|
this._userId = userId;
|
||||||
this._deviceId = deviceId;
|
this._deviceId = deviceId;
|
||||||
|
|
||||||
this._cryptoAlgorithms = [];
|
|
||||||
|
|
||||||
this._olmDevice = new OlmDevice(sessionStore);
|
this._olmDevice = new OlmDevice(sessionStore);
|
||||||
this._cryptoAlgorithms = [OLM_ALGORITHM];
|
|
||||||
|
// EncryptionAlgorithm instance for each room
|
||||||
|
this._roomAlgorithms = {};
|
||||||
|
|
||||||
// build our device keys: these will later be uploaded
|
// build our device keys: these will later be uploaded
|
||||||
this._deviceKeys = {};
|
this._deviceKeys = {};
|
||||||
@@ -152,7 +157,7 @@ function Crypto(baseApis, sessionStore, userId, deviceId) {
|
|||||||
// add our own deviceinfo to the sessionstore
|
// add our own deviceinfo to the sessionstore
|
||||||
var deviceInfo = {
|
var deviceInfo = {
|
||||||
keys: this._deviceKeys,
|
keys: this._deviceKeys,
|
||||||
algorithms: this._cryptoAlgorithms,
|
algorithms: [OLM_ALGORITHM],
|
||||||
verified: DeviceVerification.VERIFIED,
|
verified: DeviceVerification.VERIFIED,
|
||||||
};
|
};
|
||||||
var myDevices = this._sessionStore.getEndToEndDevicesForUser(
|
var myDevices = this._sessionStore.getEndToEndDevicesForUser(
|
||||||
@@ -205,7 +210,7 @@ function _uploadDeviceKeys(crypto) {
|
|||||||
var deviceId = crypto._deviceId;
|
var deviceId = crypto._deviceId;
|
||||||
|
|
||||||
var deviceKeys = {
|
var deviceKeys = {
|
||||||
algorithms: crypto._cryptoAlgorithms,
|
algorithms: [OLM_ALGORITHM],
|
||||||
device_id: deviceId,
|
device_id: deviceId,
|
||||||
keys: crypto._deviceKeys,
|
keys: crypto._deviceKeys,
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
@@ -388,7 +393,6 @@ function _storeDeviceKeys(_olmDevice, userId, deviceId, userStore, deviceResult)
|
|||||||
delete deviceResult.signatures;
|
delete deviceResult.signatures;
|
||||||
var json = anotherjson.stringify(deviceResult);
|
var json = anotherjson.stringify(deviceResult);
|
||||||
|
|
||||||
console.log("msg:", json);
|
|
||||||
try {
|
try {
|
||||||
_olmDevice.verifySignature(signKey, json, signature);
|
_olmDevice.verifySignature(signKey, json, signature);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -599,8 +603,6 @@ Crypto.prototype.isSenderKeyVerified = function(userId, algorithm, sender_key) {
|
|||||||
* @return {Object} A promise that will resolve when encryption is setup.
|
* @return {Object} A promise that will resolve when encryption is setup.
|
||||||
*/
|
*/
|
||||||
Crypto.prototype.setRoomEncryption = function(roomId, config, roomMembers) {
|
Crypto.prototype.setRoomEncryption = function(roomId, config, roomMembers) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// if we already have encryption in this room, we should ignore this event
|
// if we already have encryption in this room, we should ignore this event
|
||||||
// (for now at least. maybe we should alert the user somehow?)
|
// (for now at least. maybe we should alert the user somehow?)
|
||||||
var existingConfig = this._sessionStore.getEndToEndRoom(roomId);
|
var existingConfig = this._sessionStore.getEndToEndRoom(roomId);
|
||||||
@@ -608,23 +610,24 @@ Crypto.prototype.setRoomEncryption = function(roomId, config, roomMembers) {
|
|||||||
if (JSON.stringify(existingConfig) != JSON.stringify(config)) {
|
if (JSON.stringify(existingConfig) != JSON.stringify(config)) {
|
||||||
console.error("Ignoring m.room.encryption event which requests " +
|
console.error("Ignoring m.room.encryption event which requests " +
|
||||||
"a change of config in " + roomId);
|
"a change of config in " + roomId);
|
||||||
return;
|
return q();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.algorithm !== OLM_ALGORITHM) {
|
var AlgClass = algorithms.ENCRYPTION_CLASSES[config.algorithm];
|
||||||
throw new Error("Unknown algorithm: " + config.algorithm);
|
if (!AlgClass) {
|
||||||
|
throw new Error("Unable to encrypt with " + config.algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove spurious keys
|
// remove spurious keys
|
||||||
config = {
|
config = {
|
||||||
algorithm: OLM_ALGORITHM,
|
algorithm: config.algorithm,
|
||||||
};
|
};
|
||||||
this._sessionStore.storeEndToEndRoom(roomId, config);
|
this._sessionStore.storeEndToEndRoom(roomId, config);
|
||||||
|
|
||||||
return self.downloadKeys(roomMembers, true).then(function(res) {
|
var alg = new AlgClass(this._deviceId, this, this._olmDevice);
|
||||||
return self._ensureOlmSessionsForUsers(roomMembers);
|
this._roomAlgorithms[roomId] = alg;
|
||||||
});
|
return alg.initRoomEncryption(roomMembers);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -636,10 +639,8 @@ Crypto.prototype.setRoomEncryption = function(roomId, config, roomMembers) {
|
|||||||
* an object with keys <tt>missingUsers</tt> (a list of users with no known
|
* 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
|
* olm devices), and <tt>missingDevices</tt> a list of olm devices with no
|
||||||
* known one-time keys.
|
* known one-time keys.
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
Crypto.prototype._ensureOlmSessionsForUsers = function(users) {
|
Crypto.prototype.ensureOlmSessionsForUsers = function(users) {
|
||||||
var devicesWithoutSession = [];
|
var devicesWithoutSession = [];
|
||||||
var userWithoutDevices = [];
|
var userWithoutDevices = [];
|
||||||
for (var i = 0; i < users.length; ++i) {
|
for (var i = 0; i < users.length; ++i) {
|
||||||
@@ -710,7 +711,7 @@ Crypto.prototype._ensureOlmSessionsForUsers = function(users) {
|
|||||||
* @return {bool} whether encryption is enabled.
|
* @return {bool} whether encryption is enabled.
|
||||||
*/
|
*/
|
||||||
Crypto.prototype.isRoomEncrypted = function(roomId) {
|
Crypto.prototype.isRoomEncrypted = function(roomId) {
|
||||||
return (this._sessionStore.getEndToEndRoom(roomId) && true) || false;
|
return Boolean(this._roomAlgorithms[roomId]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -718,7 +719,7 @@ Crypto.prototype.isRoomEncrypted = function(roomId) {
|
|||||||
* Encrypt an event according to the configuration of the room, if necessary.
|
* Encrypt an event according to the configuration of the room, if necessary.
|
||||||
*
|
*
|
||||||
* @param {module:models/event.MatrixEvent} event event to be sent
|
* @param {module:models/event.MatrixEvent} event event to be sent
|
||||||
* @param {module:models/room.Room} room destination room
|
* @param {module:models/room} room destination room
|
||||||
*/
|
*/
|
||||||
Crypto.prototype.encryptEventIfNeeded = function(event, room) {
|
Crypto.prototype.encryptEventIfNeeded = function(event, room) {
|
||||||
if (event.isEncrypted()) {
|
if (event.isEncrypted()) {
|
||||||
@@ -735,112 +736,27 @@ Crypto.prototype.encryptEventIfNeeded = function(event, room) {
|
|||||||
|
|
||||||
var roomId = event.getRoomId();
|
var roomId = event.getRoomId();
|
||||||
|
|
||||||
var e2eRoomInfo = this._sessionStore.getEndToEndRoom(roomId);
|
var alg = this._roomAlgorithms[roomId];
|
||||||
if (!e2eRoomInfo || !e2eRoomInfo.algorithm) {
|
if (!alg) {
|
||||||
// not encrypting messages in this room
|
// not encrypting messages in this room
|
||||||
|
|
||||||
|
// check that the HS hasn't hidden the crypto event
|
||||||
|
if (this._sessionStore.getEndToEndRoom(roomId)) {
|
||||||
|
throw new Error(
|
||||||
|
"Room was previously configured to use encryption, but is " +
|
||||||
|
"no longer. Perhaps the homeserver is hiding the " +
|
||||||
|
"configuration event."
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var encryptedContent = this._encryptMessage(
|
var encryptedContent = alg.encryptMessage(
|
||||||
room, e2eRoomInfo, event.getType(), event.getContent()
|
room, event.getType(), event.getContent()
|
||||||
);
|
);
|
||||||
event.makeEncrypted("m.room.encrypted", encryptedContent);
|
event.makeEncrypted("m.room.encrypted", encryptedContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {module:models/room.Room} room
|
|
||||||
* @param {object} e2eRoomInfo
|
|
||||||
* @param {string} eventType
|
|
||||||
* @param {object} content
|
|
||||||
*
|
|
||||||
* @return {object} new event body
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Crypto.prototype._encryptMessage = function(room, e2eRoomInfo, eventType, content) {
|
|
||||||
if (e2eRoomInfo.algorithm !== OLM_ALGORITHM) {
|
|
||||||
throw new Error("Unknown end-to-end algorithm: " + e2eRoomInfo.algorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = this.getStoredDevicesForUser(userId);
|
|
||||||
for (var j = 0; j < devices.length; ++j) {
|
|
||||||
var dev = devices[j];
|
|
||||||
if (dev.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: room.roomId,
|
|
||||||
type: eventType,
|
|
||||||
fingerprint: participantHash,
|
|
||||||
sender_device: this._deviceId,
|
|
||||||
content: content
|
|
||||||
};
|
|
||||||
var ciphertext = {};
|
|
||||||
var payloadString = JSON.stringify(payloadJson);
|
|
||||||
for (i = 0; i < participantKeys.length; ++i) {
|
|
||||||
var deviceKey = participantKeys[i];
|
|
||||||
if (deviceKey == this._olmDevice.deviceCurve25519Key) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var sessionIds = this._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] = this._olmDevice.encryptMessage(
|
|
||||||
deviceKey, sessionId, payloadString
|
|
||||||
);
|
|
||||||
}
|
|
||||||
var encryptedContent = {
|
|
||||||
algorithm: e2eRoomInfo.algorithm,
|
|
||||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
|
||||||
ciphertext: ciphertext
|
|
||||||
};
|
|
||||||
return encryptedContent;
|
|
||||||
};
|
|
||||||
|
|
||||||
function DecryptionError(msg) {
|
|
||||||
this.message = msg;
|
|
||||||
}
|
|
||||||
utils.inherits(DecryptionError, Error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown when decryption fails
|
|
||||||
*/
|
|
||||||
Crypto.DecryptionError = DecryptionError;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt a received event
|
* Decrypt a received event
|
||||||
*
|
*
|
||||||
@@ -848,66 +764,23 @@ Crypto.DecryptionError = DecryptionError;
|
|||||||
*
|
*
|
||||||
* @return {object} decrypted payload (with properties 'type', 'content')
|
* @return {object} decrypted payload (with properties 'type', 'content')
|
||||||
*
|
*
|
||||||
* @raises {DecryptionError} if there is a problem decrypting the event
|
* @raises {algorithms.DecryptionError} if there is a problem decrypting the event
|
||||||
*/
|
*/
|
||||||
Crypto.prototype.decryptEvent = function(event) {
|
Crypto.prototype.decryptEvent = function(event) {
|
||||||
var content = event.content;
|
var content = event.content;
|
||||||
if (content.algorithm !== OLM_ALGORITHM) {
|
var AlgClass = algorithms.DECRYPTION_CLASSES[content.algorithm];
|
||||||
throw new DecryptionError("Unknown algorithm");
|
if (!AlgClass) {
|
||||||
}
|
throw new algorithms.DecryptionError("Unable to decrypt " + content.algorithm);
|
||||||
|
|
||||||
var deviceKey = content.sender_key;
|
|
||||||
var ciphertext = content.ciphertext;
|
|
||||||
|
|
||||||
if (!ciphertext) {
|
|
||||||
throw new DecryptionError("Missing ciphertext");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(this._olmDevice.deviceCurve25519Key in content.ciphertext)) {
|
|
||||||
throw new DecryptionError("Not included in recipients");
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = content.ciphertext[this._olmDevice.deviceCurve25519Key];
|
|
||||||
var sessionIds = this._olmDevice.getSessionIdsForDevice(deviceKey);
|
|
||||||
var payloadString = null;
|
|
||||||
var foundSession = false;
|
|
||||||
for (var i = 0; i < sessionIds.length; i++) {
|
|
||||||
var sessionId = sessionIds[i];
|
|
||||||
var res = this._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 = this._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 {
|
|
||||||
throw new DecryptionError("Bad Encrypted Message");
|
|
||||||
}
|
}
|
||||||
|
var alg = new AlgClass(this._deviceId, this, this._olmDevice);
|
||||||
|
return alg.decryptEvent(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see module:crypto-algorithms/base.DecryptionError
|
||||||
|
*/
|
||||||
|
Crypto.DecryptionError = algorithms.DecryptionError;
|
||||||
|
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
module.exports = Crypto;
|
module.exports = Crypto;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ module.exports.MatrixHttpApi = require("./http-api").MatrixHttpApi;
|
|||||||
module.exports.MatrixError = require("./http-api").MatrixError;
|
module.exports.MatrixError = require("./http-api").MatrixError;
|
||||||
/** The {@link module:client.MatrixClient|MatrixClient} class. */
|
/** The {@link module:client.MatrixClient|MatrixClient} class. */
|
||||||
module.exports.MatrixClient = require("./client").MatrixClient;
|
module.exports.MatrixClient = require("./client").MatrixClient;
|
||||||
/** The {@link module:models/room~Room|Room} class. */
|
/** The {@link module:models/room|Room} class. */
|
||||||
module.exports.Room = require("./models/room");
|
module.exports.Room = require("./models/room");
|
||||||
/** The {@link module:models/event-timeline~EventTimeline} class. */
|
/** The {@link module:models/event-timeline~EventTimeline} class. */
|
||||||
module.exports.EventTimeline = require("./models/event-timeline");
|
module.exports.EventTimeline = require("./models/event-timeline");
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ function synthesizeReceipt(userId, event, receiptType) {
|
|||||||
* map from event_id to timeline and index.
|
* map from event_id to timeline and index.
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
|
* @alias module:models/room
|
||||||
* @param {string} roomId Required. The ID of this room.
|
* @param {string} roomId Required. The ID of this room.
|
||||||
* @param {Object=} opts Configuration options
|
* @param {Object=} opts Configuration options
|
||||||
* @param {*} opts.storageToken Optional. The token which a data store can use
|
* @param {*} opts.storageToken Optional. The token which a data store can use
|
||||||
@@ -85,7 +86,7 @@ function synthesizeReceipt(userId, event, receiptType) {
|
|||||||
* appear in a room's timeline. If "<b>chronological</b>", messages will appear
|
* appear in a room's timeline. If "<b>chronological</b>", messages will appear
|
||||||
* in the timeline when the call to <code>sendEvent</code> was made. If
|
* in the timeline when the call to <code>sendEvent</code> was made. If
|
||||||
* "<b>detached</b>", pending messages will appear in a separate list,
|
* "<b>detached</b>", pending messages will appear in a separate list,
|
||||||
* accessbile via {@link module:models/room~Room#getPendingEvents}. Default:
|
* accessbile via {@link module:models/room#getPendingEvents}. Default:
|
||||||
* "chronological".
|
* "chronological".
|
||||||
*
|
*
|
||||||
* @param {boolean} [opts.timelineSupport = false] Set to true to enable improved
|
* @param {boolean} [opts.timelineSupport = false] Set to true to enable improved
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ var DEFAULT_PAGINATE_LOOP_LIMIT = 5;
|
|||||||
* Construct a TimelineWindow.
|
* Construct a TimelineWindow.
|
||||||
*
|
*
|
||||||
* <p>This abstracts the separate timelines in a Matrix {@link
|
* <p>This abstracts the separate timelines in a Matrix {@link
|
||||||
* module:models/room~Room|Room} into a single iterable thing. It keeps track of
|
* module:models/room|Room} into a single iterable thing. It keeps track of
|
||||||
* the start and endpoints of the window, which can be advanced with the help
|
* the start and endpoints of the window, which can be advanced with the help
|
||||||
* of pagination requests.
|
* of pagination requests.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user