Compare commits

..

27 Commits

Author SHA1 Message Date
Luke Barnard c7357952ec v0.7.2 2016-12-15 17:37:22 +00:00
Luke Barnard b796246d9d Prepare changelog for v0.7.2 2016-12-15 17:37:22 +00:00
Richard van der Hoff 2e3c349c5e Merge pull request #309 from matrix-org/rav/fix_olm_import
Bump to Olm 2.0
2016-12-14 18:59:13 +00:00
Richard van der Hoff 7f4ff352e8 Merge pull request #307 from matrix-org/rav/check_payload_length
Sanity check payload length before encrypting
2016-12-14 14:34:02 +00:00
Richard van der Hoff b11bff5a5b Bump to Olm 2.0
... since the code requires it. Also update the tests.
2016-12-14 14:15:31 +00:00
Richard van der Hoff 223bd459f6 Merge pull request #308 from matrix-org/rav/remove_dead_ohai
Remove dead _sendPingToDevice function
2016-12-14 13:56:12 +00:00
Richard van der Hoff ed1673c66c Remove dead _sendPingToDevice function
This is no longer used; remove it.
2016-12-14 12:38:33 +00:00
Richard van der Hoff 0fda43b603 Sanity check payload length before encrypting
... to give better error messages.
2016-12-14 11:54:35 +00:00
David Baker d3b63c592e Set GIT_COMITTER_EMAIL
so the release tag verifies correctly
2016-12-12 15:37:09 +00:00
Luke Barnard a32af7d77c Merge pull request #306 from matrix-org/luke/feature-AS-room-publication
Add setRoomDirectoryVisibilityAppService
2016-12-12 14:20:43 +00:00
Luke Barnard 742d942baa Add setRoomDirectoryVisibilityAppService
This is for setting the publicity of a room that is bridged to a 3rd party network. This change reflects the second bullet point of https://github.com/matrix-org/synapse/pull/1676#issue-193810881.
2016-12-12 13:52:44 +00:00
David Baker 73e86bfc5d Don't depend on having a build_dir
to get latest changes
2016-12-09 20:17:25 +00:00
David Baker 0a4c41c958 Merge branch 'master' into develop 2016-12-09 19:30:15 +00:00
David Baker 79a699f0be v0.7.1 2016-12-09 19:29:13 +00:00
David Baker 4a2e6a826b Prepare changelog for v0.7.1 2016-12-09 19:29:12 +00:00
David Baker 95f56f95ec Merge pull request #305 from matrix-org/dbkr/signed_releases
Update release script to do signed releases
2016-12-09 19:22:01 +00:00
David Baker 67e75fb7af Invoke changelog_head from outside the pushd
so we know the relative path is right
2016-12-09 19:19:40 +00:00
David Baker ebda89d1ae Add changelog_head.py 2016-12-09 17:34:11 +00:00
David Baker 5ce5299651 Update release script to do signed releases
if a signing ID is set in release_config.yaml

Also set the release text to the relevant changelog entry
2016-12-09 17:04:19 +00:00
David Baker 41bd518182 Merge pull request #304 from matrix-org/rav/await_pending_downloads
e2e: Wait for pending device lists
2016-12-08 18:41:53 +00:00
Richard van der Hoff 739e94302d fix test fail 2016-12-08 18:06:22 +00:00
Richard van der Hoff e54541aecf e2e: Wait for pending device lists
When we send a megolm message, wait for any existing key download to complete.
2016-12-08 16:37:29 +00:00
Richard van der Hoff 338c707579 Merge pull request #303 from matrix-org/rav/new_session_after_blacklist
Start a new megolm session when devices are blacklisted
2016-12-08 16:08:27 +00:00
Richard van der Hoff 301ab01911 Start a new megolm session when devices are blacklisted
If we have shared the session with a device which is subsequently blacklisted,
we need to start a new session for the next message.

Rather than doing this proactively (which would be subject to false-positives
and require slightly awkward tracking of who we had shared the session with),
we check the list of who we have shared the session with on each send, and
start a new session if any of them are blocked.

Fixes https://github.com/vector-im/riot-web/issues/2146.
2016-12-08 13:39:58 +00:00
Richard van der Hoff 99089c0f5f Merge pull request #302 from matrix-org/rav/send_to_our_devices
E2E: Download our own devicelist on startup
2016-12-07 11:43:24 +00:00
Richard van der Hoff ec124847d7 Test for self-keyshare
Make sure that we share keys with our own devices.
2016-12-07 11:14:36 +00:00
Richard van der Hoff 89ced19874 E2E: Download our own devicelist on startup
Make sure we get a list of our own devices when starting a new client.

Fixes https://github.com/vector-im/riot-web/issues/2676.
2016-12-06 17:09:21 +00:00
12 changed files with 854 additions and 401 deletions
+28
View File
@@ -1,3 +1,31 @@
Changes in [0.7.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.7.2) (2016-12-15)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.7.1...v0.7.2)
* Bump to Olm 2.0
[\#309](https://github.com/matrix-org/matrix-js-sdk/pull/309)
* Sanity check payload length before encrypting
[\#307](https://github.com/matrix-org/matrix-js-sdk/pull/307)
* Remove dead _sendPingToDevice function
[\#308](https://github.com/matrix-org/matrix-js-sdk/pull/308)
* Add setRoomDirectoryVisibilityAppService
[\#306](https://github.com/matrix-org/matrix-js-sdk/pull/306)
* Update release script to do signed releases
[\#305](https://github.com/matrix-org/matrix-js-sdk/pull/305)
* e2e: Wait for pending device lists
[\#304](https://github.com/matrix-org/matrix-js-sdk/pull/304)
* Start a new megolm session when devices are blacklisted
[\#303](https://github.com/matrix-org/matrix-js-sdk/pull/303)
* E2E: Download our own devicelist on startup
[\#302](https://github.com/matrix-org/matrix-js-sdk/pull/302)
Changes in [0.7.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.7.1) (2016-12-09)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.7.1-rc.1...v0.7.1)
No changes
Changes in [0.7.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.7.1-rc.1) (2016-12-05)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.7.0...v0.7.1-rc.1)
+24
View File
@@ -570,6 +570,30 @@ MatrixBaseApis.prototype.setRoomDirectoryVisibility =
);
};
/**
* Set the visbility of a room bridged to a 3rd party network in
* the current HS's room directory.
* @param {string} networkId the network ID of the 3rd party
* instance under which this room is published under.
* @param {string} roomId
* @param {string} visibility "public" to make the room visible
* in the public directory, or "private" to make
* it invisible.
* @param {module:client.callback} callback Optional.
* @return {module:client.Promise} Resolves: result object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.setRoomDirectoryVisibilityAppService =
function(networkId, roomId, visibility, callback) {
var path = utils.encodeUri("/directory/list/appservice/$networkId/$roomId", {
$networkId: networkId,
$roomId: roomId
});
return this._http.authedRequest(
callback, "PUT", path, undefined, { "visibility": visibility }
);
};
// Media operations
// ================
+27 -4
View File
@@ -20,10 +20,33 @@ limitations under the License.
*
* @module crypto/OlmDevice
*/
var Olm = require("olm");
var utils = require("../utils");
// The maximum size of an event is 65K, and we base64 the content, so this is a
// reasonable approximation to the biggest plaintext we can encrypt.
var MAX_PLAINTEXT_LENGTH = 65536 * 3 / 4;
function checkPayloadLength(payloadString) {
if (payloadString === undefined) {
throw new Error("payloadString undefined");
}
if (payloadString.length > MAX_PLAINTEXT_LENGTH) {
// might as well fail early here rather than letting the olm library throw
// a cryptic memory allocation error.
//
// Note that even if we manage to do the encryption, the message send may fail,
// because by the time we've wrapped the ciphertext in the event object, it may
// exceed 65K. But at least we won't just fail with "abort()" in that case.
throw new Error("Message too long (" + payloadString.length + " bytes). " +
"The maximum for an encrypted message is " +
MAX_PLAINTEXT_LENGTH + " bytes.");
}
}
/**
* Manages the olm cryptography functions. Each OlmDevice has a single
* OlmAccount and a number of OlmSessions.
@@ -386,9 +409,7 @@ OlmDevice.prototype.encryptMessage = function(
) {
var self = this;
if (payloadString === undefined) {
throw new Error("payloadString undefined");
}
checkPayloadLength(payloadString);
return this._getSession(theirDeviceIdentityKey, sessionId, function(session) {
var res = session.encrypt(payloadString);
@@ -515,6 +536,8 @@ OlmDevice.prototype.createOutboundGroupSession = function() {
OlmDevice.prototype.encryptGroupMessage = function(sessionId, payloadString) {
var self = this;
checkPayloadLength(payloadString);
return this._getOutboundGroupSession(sessionId, function(session) {
var res = session.encrypt(payloadString);
self._saveOutboundGroupSession(session);
+108 -61
View File
@@ -36,8 +36,6 @@ var base = require("./base");
* @property {string} sessionId
* @property {Number} useCount number of times this session has been used
* @property {Number} creationTime when the session was created (ms since the epoch)
* @property {module:client.Promise?} sharePromise If a share operation is in progress,
* a promise which resolves when it is complete.
*
* @property {object} sharedWithDevices
* devices with which we have shared the session key
@@ -47,7 +45,6 @@ function OutboundSessionInfo(sessionId) {
this.sessionId = sessionId;
this.useCount = 0;
this.creationTime = new Date().getTime();
this.sharePromise = null;
this.sharedWithDevices = {};
}
@@ -78,6 +75,45 @@ OutboundSessionInfo.prototype.needsRotation = function(
};
/**
* Determine if this session has been shared with devices which it shouldn't
* have been.
*
* @param {Object} devicesInRoom userId -> {deviceId -> object}
* devices we should shared the session with.
*
* @return {Boolean} true if we have shared the session with devices which aren't
* in devicesInRoom.
*/
OutboundSessionInfo.prototype.sharedWithTooManyDevices = function(
devicesInRoom
) {
for (var userId in this.sharedWithDevices) {
if (!this.sharedWithDevices.hasOwnProperty(userId)) { continue; }
if (!devicesInRoom.hasOwnProperty(userId)) {
console.log("Starting new session because we shared with " + userId);
return true;
}
for (var deviceId in this.sharedWithDevices[userId]) {
if (!this.sharedWithDevices[userId].hasOwnProperty(deviceId)) {
continue;
}
if (!devicesInRoom[userId].hasOwnProperty(deviceId)) {
console.log(
"Starting new session because we shared with " +
userId + ":" + deviceId
);
return true;
}
}
}
};
/**
* Megolm encryption implementation
*
@@ -90,10 +126,12 @@ OutboundSessionInfo.prototype.needsRotation = function(
function MegolmEncryption(params) {
base.EncryptionAlgorithm.call(this, params);
// OutboundSessionInfo. Null if we haven't yet started setting one up. Note
// that even if this is non-null, it may not be ready for use (in which
// case _outboundSession.sharePromise will be non-null.)
this._outboundSession = null;
// the most recent attempt to set up a session. This is used to serialise
// the session setups, so that we have a race-free view of which session we
// are using, and which devices we have shared the keys with. It resolves
// with an OutboundSessionInfo (or undefined, for the first message in the
// room).
this._setupPromise = q();
// default rotation periods
this._sessionRotationPeriodMsgs = 100;
@@ -117,25 +155,37 @@ utils.inherits(MegolmEncryption, base.EncryptionAlgorithm);
* @return {module:client.Promise} Promise which resolves to the
* OutboundSessionInfo when setup is complete.
*/
MegolmEncryption.prototype._ensureOutboundSession = function(room) {
MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
var self = this;
var session = this._outboundSession;
var session;
// need to make a brand new session?
if (!session || session.needsRotation(self._sessionRotationPeriodMsgs,
self._sessionRotationPeriodMs)
) {
this._outboundSession = session = this._prepareNewSession(room);
}
// takes the previous OutboundSessionInfo, and considers whether to create
// a new one. Also shares the key with any (new) devices in the room.
// Updates `session` to hold the final OutboundSessionInfo.
//
// returns a promise which resolves once the keyshare is successful.
function prepareSession(oldSession) {
session = oldSession;
if (session.sharePromise) {
// key share already in progress
return session.sharePromise;
}
// need to make a brand new session?
if (session && session.needsRotation(self._sessionRotationPeriodMsgs,
self._sessionRotationPeriodMs)
) {
console.log("Starting new megolm session because we need to rotate.");
session = null;
}
// no share in progress: check if we need to share with any devices
var prom = this._getDevicesInRoom(room).then(function(devicesInRoom) {
// determine if we have shared with anyone we shouldn't have
if (session && session.sharedWithTooManyDevices(devicesInRoom)) {
session = null;
}
if (!session) {
session = self._prepareNewSession();
}
// now check if we need to share with any devices
var shareMap = {};
for (var userId in devicesInRoom) {
@@ -152,10 +202,6 @@ MegolmEncryption.prototype._ensureOutboundSession = function(room) {
var deviceInfo = userDevices[deviceId];
if (deviceInfo.isBlocked()) {
continue;
}
var key = deviceInfo.getIdentityKey();
if (key == self._olmDevice.deviceCurve25519Key) {
// don't bother sending to ourself
@@ -175,24 +221,27 @@ MegolmEncryption.prototype._ensureOutboundSession = function(room) {
return self._shareKeyWithDevices(
session, shareMap
);
}).finally(function() {
session.sharePromise = null;
}).then(function() {
return session;
});
}
session.sharePromise = prom;
return prom;
// helper which returns the session prepared by prepareSession
function returnSession() { return session; }
// first wait for the previous share to complete
var prom = this._setupPromise.then(prepareSession);
// _setupPromise resolves to `session` whether or not the share succeeds
this._setupPromise = prom.then(returnSession, returnSession);
// but we return a promise which only resolves if the share was successful.
return prom.then(returnSession);
};
/**
* @private
*
* @param {module:models/room} room
*
* @return {module:crypto/algorithms/megolm.OutboundSessionInfo} session
*/
MegolmEncryption.prototype._prepareNewSession = function(room) {
MegolmEncryption.prototype._prepareNewSession = function() {
var session_id = this._olmDevice.createOutboundGroupSession();
var key = this._olmDevice.getOutboundGroupSessionKey(session_id);
@@ -335,7 +384,9 @@ MegolmEncryption.prototype._shareKeyWithDevices = function(session, devicesByUse
*/
MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
var self = this;
return this._ensureOutboundSession(room).then(function(session) {
return this._getDevicesInRoom(room).then(function(devicesInRoom) {
return self._ensureOutboundSession(devicesInRoom);
}).then(function(session) {
var payloadJson = {
room_id: self._roomId,
type: eventType,
@@ -362,30 +413,7 @@ MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
};
/**
* @inheritdoc
*
* @param {module:models/event.MatrixEvent} event event causing the change
* @param {module:models/room-member} member user whose membership changed
* @param {string=} oldMembership previous membership
*/
MegolmEncryption.prototype.onRoomMembership = function(event, member, oldMembership) {
var newMembership = member.membership;
if (newMembership === 'join' || newMembership === 'invite') {
return;
}
// otherwise we assume the user is leaving, and start a new outbound session.
console.log("Discarding outbound megolm session due to change in " +
"membership of " + member.userId + " (" + oldMembership +
"->" + newMembership + ")");
// this ensures that we will start a new session on the next message.
this._outboundSession = null;
};
/**
* Get the list of devices for all users in the room
* Get the list of unblocked devices for all users in the room
*
* @param {module:models/room} room
*
@@ -402,7 +430,26 @@ MegolmEncryption.prototype._getDevicesInRoom = function(room) {
// have a list of the user's devices, then we already share an e2e room
// with them, which means that they will have announced any new devices via
// an m.new_device.
return this._crypto.downloadKeys(roomMembers, false);
return this._crypto.downloadKeys(roomMembers, false).then(function(devices) {
// remove any blocked devices
for (var userId in devices) {
if (!devices.hasOwnProperty(userId)) {
continue;
}
var userDevices = devices[userId];
for (var deviceId in userDevices) {
if (!userDevices.hasOwnProperty(deviceId)) {
continue;
}
if (userDevices[deviceId].isBlocked()) {
delete userDevices[deviceId];
}
}
}
return devices;
});
};
/**
+88 -105
View File
@@ -55,8 +55,10 @@ function Crypto(baseApis, eventEmitter, sessionStore, userId, deviceId) {
this._deviceId = deviceId;
this._initialSyncCompleted = false;
// userId -> deviceId -> true
this._pendingNewDevices = {};
// userId -> true
this._pendingUsersWithNewDevices = {};
// userId -> [promise, ...]
this._keyDownloadsInProgressByUser = {};
this._olmDevice = new OlmDevice(sessionStore);
@@ -77,24 +79,32 @@ function Crypto(baseApis, eventEmitter, sessionStore, userId, deviceId) {
this._deviceKeys["curve25519:" + this._deviceId] =
this._olmDevice.deviceCurve25519Key;
// add our own deviceinfo to the sessionstore
var deviceInfo = {
keys: this._deviceKeys,
algorithms: this._supportedAlgorithms,
verified: DeviceVerification.VERIFIED,
};
var myDevices = this._sessionStore.getEndToEndDevicesForUser(
this._userId
) || {};
myDevices[this._deviceId] = deviceInfo;
this._sessionStore.storeEndToEndDevicesForUser(
this._userId, myDevices
);
_registerEventHandlers(this, eventEmitter);
if (!myDevices) {
// we don't yet have a list of our own devices; make sure we
// get one when we flush the pendingUsersWithNewDevices.
this._pendingUsersWithNewDevices[this._userId] = true;
myDevices = {};
}
// map from userId -> deviceId -> roomId -> timestamp
this._lastNewDeviceMessageTsByUserDeviceRoom = {};
if (!myDevices[this._deviceId]) {
// add our own deviceinfo to the sessionstore
var deviceInfo = {
keys: this._deviceKeys,
algorithms: this._supportedAlgorithms,
verified: DeviceVerification.VERIFIED,
};
myDevices[this._deviceId] = deviceInfo;
this._sessionStore.storeEndToEndDevicesForUser(
this._userId, myDevices
);
}
_registerEventHandlers(this, eventEmitter);
}
function _registerEventHandlers(crypto, eventEmitter) {
@@ -274,52 +284,67 @@ function _uploadOneTimeKeys(crypto) {
Crypto.prototype.downloadKeys = function(userIds, forceDownload) {
var self = this;
// map from userid -> deviceid -> DeviceInfo
var stored = {};
function storeDev(userId, dev) {
stored[userId][dev.deviceId] = dev;
}
// promises we need to wait for while the download happens
var promises = [];
// list of userids we need to download keys for
var downloadUsers = [];
function perUserCatch(u) {
return function(e) {
console.warn('Error downloading keys for user ' + u + ':', e);
};
}
if (forceDownload) {
downloadUsers = userIds;
} else {
for (var i = 0; i < userIds.length; ++i) {
var userId = userIds[i];
var devices = this.getStoredDevicesForUser(userId);
var u = userIds[i];
if (!devices) {
downloadUsers.push(userId);
} else {
stored[userId] = {};
devices.map(storeDev.bind(null, userId));
var inprogress = this._keyDownloadsInProgressByUser[u];
if (inprogress) {
// wait for the download to complete
promises.push(q.any(inprogress).catch(perUserCatch(u)));
} else if (!this.getStoredDevicesForUser(u)) {
downloadUsers.push(u);
}
}
}
if (downloadUsers.length === 0) {
return q(stored);
if (downloadUsers.length > 0) {
var r = this._doKeyDownloadForUsers(downloadUsers);
downloadUsers.map(function(u) {
promises.push(r[u].catch(perUserCatch(u)));
});
}
var r = this._doKeyDownloadForUsers(downloadUsers);
var promises = [];
downloadUsers.map(function(u) {
promises.push(r[u].catch(function(e) {
console.warn('Error downloading keys for user ' + u + ':', e);
}).then(function() {
stored[u] = {};
var devices = self.getStoredDevicesForUser(u) || [];
devices.map(storeDev.bind(null, u));
}));
});
return q.all(promises).then(function() {
return stored;
return self._getDevicesFromStore(userIds);
});
};
/**
* Get the stored device keys for a list of user ids
*
* @param {string[]} userIds the list of users to list keys for.
*
* @return {Object} userId->deviceId->{@link module:crypto/deviceinfo|DeviceInfo}.
*/
Crypto.prototype._getDevicesFromStore = function(userIds) {
var stored = {};
var self = this;
userIds.map(function(u) {
stored[u] = {};
var devices = self.getStoredDevicesForUser(u) || [];
devices.map(function(dev) {
stored[u][dev.deviceId] = dev;
});
});
return stored;
};
/**
* @param {string[]} downloadUsers list of userIds
*
@@ -334,8 +359,23 @@ Crypto.prototype._doKeyDownloadForUsers = function(downloadUsers) {
var promiseMap = {};
downloadUsers.map(function(u) {
deferMap[u] = q.defer();
promiseMap[u] = deferMap[u].promise;
var deferred = q.defer();
var promise = deferred.promise.finally(function() {
var inProgress = self._keyDownloadsInProgressByUser[u];
utils.removeElement(inProgress, function(e) { return e === promise; });
if (inProgress.length === 0) {
// no more downloads for this user; remove the element
delete self._keyDownloadsInProgressByUser[u];
}
});
if (!self._keyDownloadsInProgressByUser[u]) {
self._keyDownloadsInProgressByUser[u] = [];
}
self._keyDownloadsInProgressByUser[u].push(promise);
deferMap[u] = deferred;
promiseMap[u] = promise;
});
this._baseApis.downloadKeysForUsers(
@@ -919,58 +959,6 @@ Crypto.prototype.decryptEvent = function(event) {
alg.decryptEvent(event);
};
/**
* Send a "m.new_device" message to remind it that we exist and are a member
* of a room.
*
* This is rate limited to send a message at most once an hour per desination.
*
* @param {string} userId The ID of the user to ping.
* @param {string?} deviceId The ID of the device to ping. If null, all
* devices.
* @param {string} roomId The ID of the room we want to remind them about.
*/
Crypto.prototype._sendPingToDevice = function(userId, deviceId, roomId) {
if (deviceId === null) {
deviceId = "*";
}
var lastMessageTsMap = this._lastNewDeviceMessageTsByUserDeviceRoom;
var lastTsByDevice = lastMessageTsMap[userId];
if (!lastTsByDevice) {
lastTsByDevice = lastMessageTsMap[userId] = {};
}
var lastTsByRoom = lastTsByDevice[deviceId];
if (!lastTsByRoom) {
lastTsByRoom = lastTsByDevice[deviceId] = {};
}
var lastTs = lastTsByRoom[roomId];
var timeNowMs = Date.now();
var oneHourMs = 1000 * 60 * 60;
if (lastTs !== undefined && lastTs + oneHourMs > timeNowMs) {
// rate-limiting
return;
}
var content = {};
content[userId] = {};
content[userId][deviceId] = {
device_id: this._deviceId,
rooms: [roomId],
};
this._baseApis.sendToDevice(
"m.new_device", // OH HAI!
content
).done();
lastTsByRoom[roomId] = timeNowMs;
};
/**
* handle an m.room.encryption event
*
@@ -1134,8 +1122,7 @@ Crypto.prototype._onNewDeviceEvent = function(event) {
return;
}
this._pendingNewDevices[userId] = this._pendingNewDevices[userId] || {};
this._pendingNewDevices[userId][deviceId] = true;
this._pendingUsersWithNewDevices[userId] = true;
// we delay handling these until the intialsync has completed, so that we
// can do all of them together.
@@ -1150,10 +1137,7 @@ Crypto.prototype._onNewDeviceEvent = function(event) {
Crypto.prototype._flushNewDeviceRequests = function() {
var self = this;
var pending = this._pendingNewDevices;
var users = utils.keys(pending).filter(function(u) {
return utils.keys(pending[u]).length > 0;
});
var users = utils.keys(this._pendingUsersWithNewDevices);
if (users.length === 0) {
return;
@@ -1163,7 +1147,7 @@ Crypto.prototype._flushNewDeviceRequests = function() {
// we've kicked off requests to these users: remove their
// pending flag for now.
this._pendingNewDevices = {};
this._pendingUsersWithNewDevices = {};
users.map(function(u) {
r[u] = r[u].catch(function(e) {
@@ -1175,8 +1159,7 @@ Crypto.prototype._flushNewDeviceRequests = function() {
// mean that we will do another download in the future, but won't
// tight-loop.
//
self._pendingNewDevices[u] = self._pendingNewDevices[u] || {};
utils.update(self._pendingNewDevices[u], pending[u]);
self._pendingUsersWithNewDevices[u] = true;
});
});
+2 -1
View File
@@ -159,9 +159,10 @@ module.exports.ensureOlmSessionsForDevices = function(
return baseApis.claimOneTimeKeys(
devicesWithoutSession, oneTimeKeyAlgorithm
).then(function(res) {
var otk_res = res.one_time_keys || {};
for (var userId in devicesByUser) {
if (!devicesByUser.hasOwnProperty(userId)) { continue; }
var userRes = res.one_time_keys[userId] || {};
var userRes = otk_res[userId] || {};
var devices = devicesByUser[userId];
for (var j = 0; j < devices.length; j++) {
var deviceInfo = devices[j];
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "0.7.1-rc.1",
"version": "0.7.2",
"description": "Matrix Client-Server SDK for Javascript",
"main": "index.js",
"scripts": {
@@ -56,6 +56,6 @@
"watchify": "^3.2.1"
},
"optionalDependencies": {
"olm": "https://matrix.org/packages/npm/olm/olm-1.3.0.tgz"
"olm": "https://matrix.org/packages/npm/olm/olm-2.0.0.tgz"
}
}
+40 -5
View File
@@ -63,6 +63,8 @@ if [ -z "$skip_changelog" ]; then
# update_changelog doesn't have a --version flag
update_changelog -h > /dev/null || (echo "github-changelog-generator is required: please install it"; exit)
fi
latest_changes=`mktemp`
cat "${changelog_file}" | `dirname $0`/scripts/changelog_head.py > "${latest_changes}"
# ignore leading v on release
release="${1#v}"
@@ -114,16 +116,24 @@ fi
set -x
# Bump package.json, build the dist, and tag
# Bump package.json and build the dist
echo "npm version"
# npm version will automatically commit its modification
# and make a release tag. We don't want it to create the tag
# because github will do that, but we can only turn off both
# of these behaviours, so we have to manually commit the
# result.
# because it can only sign with the default key, but we can
# only turn off both of these behaviours, so we have to
# manually commit the result.
npm version --no-git-tag-version "$release"
git commit package.json -m "$tag"
# figure out if we should be signing this release
signing_id=
if [ -f release_config.yaml ]; then
signing_id=`cat release_config.yaml | python -c "import yaml; import sys; print yaml.load(sys.stdin)['signing_id']"`
fi
# If there is a 'dist' script in the package.json,
# run it in a separate checkout of the project, then
# upload any files in the 'dist' directory as release
@@ -145,9 +155,16 @@ if [ $dodist -eq 0 ]; then
# We haven't tagged yet, so tell the dist script what version
# it's building
DIST_VERSION="$tag" npm run dist
popd
for i in "$builddir"/dist/*; do
assets="$assets -a $i"
if [ -n "$signing_id" ]
then
gpg -u "$signing_id" --armor --output "$i".asc --detach-sig "$i"
assets="$assets -a $i.asc"
fi
done
fi
@@ -155,15 +172,33 @@ fi
# a branch it doesn't have)
git push origin "$rel_branch"
if [ -n "$signing_id" ]; then
# make a signed tag
# gnupg seems to fail to get the right tty device unless we set it here
GIT_COMMITTER_EMAIL="$signing_id" GPG_TTY=`tty` git tag -u "$signing_id" -F "${latest_changes}" "$tag"
else
git tag -a -F "${latest_changes}" "$tag"
fi
# push the tag
git push origin "$tag"
hubflags=''
if [ $prerelease -eq 1 ]; then
hubflags='-p'
fi
hub release create $hubflags $assets -m "$tag" "$tag"
release_text=`mktemp`
echo "$tag" > "${release_text}"
echo >> "${release_text}"
cat "${latest_changes}" >> "${release_text}"
hub release create $hubflags $assets -f "${release_text}" "$tag"
if [ $dodist -eq 0 ]; then
rm -rf "$builddir"
fi
rm "${release_text}"
rm "${latest_changes}"
if [ -z "$skip_jsdoc" ]; then
echo "generating jsdocs"
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env python
"""
Outputs the body of the first entry of changelog file on stdin
"""
import re
import sys
found_first_header = False
for line in sys.stdin:
line = line.strip()
if re.match(r"^Changes in \[.*\]", line):
if found_first_header:
break
found_first_header = True
elif not re.match(r"^=+$", line) and len(line) > 0:
print line
+18
View File
@@ -383,6 +383,15 @@ function recvMessage(httpBackend, client, sender, message) {
function aliStartClient() {
expectAliKeyUpload().catch(test_utils.failTest);
// ali will try to query her own keys on start
aliHttpBackend.when("POST", "/keys/query").respond(200, function(path, content) {
expect(content.device_keys[aliUserId]).toEqual({});
var result = {};
result[aliUserId] = {};
return {device_keys: result};
});
startClient(aliHttpBackend, aliClient);
return aliHttpBackend.flush().then(function() {
console.log("Ali client started");
@@ -391,6 +400,15 @@ function aliStartClient() {
function bobStartClient() {
expectBobKeyUpload().catch(test_utils.failTest);
// bob will try to query his own keys on start
bobHttpBackend.when("POST", "/keys/query").respond(200, function(path, content) {
expect(content.device_keys[bobUserId]).toEqual({});
var result = {};
result[bobUserId] = {};
return {device_keys: result};
});
startClient(bobHttpBackend, bobClient);
return bobHttpBackend.flush().then(function() {
console.log("Bob client started");
+498 -222
View File
@@ -29,6 +29,8 @@ var utils = require('../../lib/utils');
var test_utils = require('../test-utils');
var MockHttpBackend = require('../mock-request');
var ROOM_ID = "!room:id";
/**
* Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient
*
@@ -59,12 +61,25 @@ function TestClient(userId, deviceId, accessToken) {
/**
* start the client, and wait for it to initialise.
*
* @param {object?} deviceQueryResponse the list of our existing devices to return from
* the /query request. Defaults to empty device list
* @return {Promise}
*/
TestClient.prototype.start = function() {
TestClient.prototype.start = function(existingDevices) {
var self = this;
this.httpBackend.when("GET", "/pushrules").respond(200, {});
this.httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
this.httpBackend.when('POST', '/keys/query').respond(200, function(path, content) {
expect(content.device_keys[self.userId]).toEqual({});
var res = existingDevices;
if (!res) {
res = { device_keys: {} };
res.device_keys[self.userId] = {};
}
return res;
});
this.httpBackend.when("POST", "/keys/upload").respond(200, function(path, content) {
expect(content.one_time_keys).not.toBeDefined();
expect(content.device_keys).toBeDefined();
@@ -243,19 +258,122 @@ function encryptGroupSessionKey(opts) {
});
}
/**
* get a /sync response which contains a single room (ROOM_ID),
* with the members given
*
* @param {string[]} roomMembers
*
* @return {object} event
*/
function getSyncResponse(roomMembers) {
var roomResponse = {
state: {
events: [
test_utils.mkEvent({
type: 'm.room.encryption',
skey: '',
content: {
algorithm: 'm.megolm.v1.aes-sha2',
},
}),
],
}
};
for (var i = 0; i < roomMembers.length; i++) {
roomResponse.state.events.push(
test_utils.mkMembership({
mship: 'join',
sender: roomMembers[i],
})
);
}
var syncResponse = {
next_batch: 1,
rooms: {
join: {},
},
};
syncResponse.rooms.join[ROOM_ID] = roomResponse;
return syncResponse;
}
describe("megolm", function() {
if (!sdk.CRYPTO_ENABLED) {
return;
}
var ROOM_ID = "!room:id";
var testOlmAccount;
var testSenderKey;
var aliceTestClient;
var testDeviceKeys;
beforeEach(function(done) {
/**
* Get the device keys for testOlmAccount in a format suitable for a
* response to /keys/query
*/
function getTestKeysQueryResponse(userId) {
var testE2eKeys = JSON.parse(testOlmAccount.identity_keys());
var testDeviceKeys = {
algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'],
device_id: 'DEVICE_ID',
keys: {
'curve25519:DEVICE_ID': testE2eKeys.curve25519,
'ed25519:DEVICE_ID': testE2eKeys.ed25519,
},
user_id: userId,
};
var j = anotherjson.stringify(testDeviceKeys);
var sig = testOlmAccount.sign(j);
testDeviceKeys.signatures = {};
testDeviceKeys.signatures[userId] = {
'ed25519:DEVICE_ID': sig,
};
var queryResponse = {
device_keys: {},
};
queryResponse.device_keys[userId] = {
'DEVICE_ID': testDeviceKeys,
};
return queryResponse;
}
/**
* Get a one-time key for testOlmAccount in a format suitable for a
* response to /keys/claim
*/
function getTestKeysClaimResponse(userId) {
testOlmAccount.generate_one_time_keys(1);
var testOneTimeKeys = JSON.parse(testOlmAccount.one_time_keys());
testOlmAccount.mark_keys_as_published();
var keyId = utils.keys(testOneTimeKeys.curve25519)[0];
var oneTimeKey = testOneTimeKeys.curve25519[keyId];
var keyResult = {
'key': oneTimeKey,
};
var j = anotherjson.stringify(keyResult);
var sig = testOlmAccount.sign(j);
keyResult.signatures = {};
keyResult.signatures[userId] = {
'ed25519:DEVICE_ID': sig,
};
var claimResponse = {one_time_keys: {}};
claimResponse.one_time_keys[userId] = {
'DEVICE_ID': {},
};
claimResponse.one_time_keys[userId].DEVICE_ID['signed_curve25519:' + keyId] =
keyResult;
return claimResponse;
}
beforeEach(function() {
test_utils.beforeEach(this);
aliceTestClient = new TestClient(
@@ -266,25 +384,6 @@ describe("megolm", function() {
testOlmAccount.create();
var testE2eKeys = JSON.parse(testOlmAccount.identity_keys());
testSenderKey = testE2eKeys.curve25519;
testDeviceKeys = {
algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'],
device_id: 'DEVICE_ID',
keys: {
'curve25519:DEVICE_ID': testE2eKeys.curve25519,
'ed25519:DEVICE_ID': testE2eKeys.ed25519,
},
user_id: '@bob:xyz',
};
var j = anotherjson.stringify(testDeviceKeys);
var sig = testOlmAccount.sign(j);
testDeviceKeys.signatures = {
'@bob:xyz': {
'ed25519:DEVICE_ID': sig,
},
};
return aliceTestClient.start().nodeify(done);
});
afterEach(function() {
@@ -292,44 +391,47 @@ describe("megolm", function() {
});
it("Alice receives a megolm message", function(done) {
var p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
return aliceTestClient.start().then(function() {
var p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
var groupSession = new Olm.OutboundGroupSession();
groupSession.create();
var groupSession = new Olm.OutboundGroupSession();
groupSession.create();
// make the room_key event
var roomKeyEncrypted = encryptGroupSessionKey({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
groupSession: groupSession,
room_id: ROOM_ID,
});
// make the room_key event
var roomKeyEncrypted = encryptGroupSessionKey({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
groupSession: groupSession,
room_id: ROOM_ID,
});
// encrypt a message with the group session
var messageEncrypted = encryptMegolmEvent({
senderKey: testSenderKey,
groupSession: groupSession,
room_id: ROOM_ID,
});
// encrypt a message with the group session
var messageEncrypted = encryptMegolmEvent({
senderKey: testSenderKey,
groupSession: groupSession,
room_id: ROOM_ID,
});
// Alice gets both the events in a single sync
var syncResponse = {
next_batch: 1,
to_device: {
events: [roomKeyEncrypted],
},
rooms: {
join: {},
},
};
syncResponse.rooms.join[ROOM_ID] = {
timeline: {
events: [messageEncrypted],
},
};
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.httpBackend.flush("/sync", 1).then(function() {
// Alice gets both the events in a single sync
var syncResponse = {
next_batch: 1,
to_device: {
events: [roomKeyEncrypted],
},
rooms: {
join: {},
},
};
syncResponse.rooms.join[ROOM_ID] = {
timeline: {
events: [messageEncrypted],
},
};
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.httpBackend.flush("/sync", 1);
}).then(function() {
var room = aliceTestClient.client.getRoom(ROOM_ID);
var event = room.getLiveTimeline().getEvents()[0];
expect(event.getContent().body).toEqual('42');
@@ -337,65 +439,67 @@ describe("megolm", function() {
});
it("Alice gets a second room_key message", function(done) {
var p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
return aliceTestClient.start().then(function() {
var p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
var groupSession = new Olm.OutboundGroupSession();
groupSession.create();
var groupSession = new Olm.OutboundGroupSession();
groupSession.create();
// make the room_key event
var roomKeyEncrypted1 = encryptGroupSessionKey({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
groupSession: groupSession,
room_id: ROOM_ID,
});
// make the room_key event
var roomKeyEncrypted1 = encryptGroupSessionKey({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
groupSession: groupSession,
room_id: ROOM_ID,
});
// encrypt a message with the group session
var messageEncrypted = encryptMegolmEvent({
senderKey: testSenderKey,
groupSession: groupSession,
room_id: ROOM_ID,
});
// encrypt a message with the group session
var messageEncrypted = encryptMegolmEvent({
senderKey: testSenderKey,
groupSession: groupSession,
room_id: ROOM_ID,
});
// make a second room_key event now that we have advanced the group
// session.
var roomKeyEncrypted2 = encryptGroupSessionKey({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
groupSession: groupSession,
room_id: ROOM_ID,
});
// make a second room_key event now that we have advanced the group
// session.
var roomKeyEncrypted2 = encryptGroupSessionKey({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
groupSession: groupSession,
room_id: ROOM_ID,
});
// on the first sync, send the best room key
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
next_batch: 1,
to_device: {
events: [roomKeyEncrypted1],
},
});
// on the first sync, send the best room key
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
next_batch: 1,
to_device: {
events: [roomKeyEncrypted1],
},
});
// on the second sync, send the advanced room key, along with the
// message. This simulates the situation where Alice has been sent a
// later copy of the room key and is reloading the client.
var syncResponse2 = {
next_batch: 2,
to_device: {
events: [roomKeyEncrypted2],
},
rooms: {
join: {},
},
};
syncResponse2.rooms.join[ROOM_ID] = {
timeline: {
events: [messageEncrypted],
},
};
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse2);
// on the second sync, send the advanced room key, along with the
// message. This simulates the situation where Alice has been sent a
// later copy of the room key and is reloading the client.
var syncResponse2 = {
next_batch: 2,
to_device: {
events: [roomKeyEncrypted2],
},
rooms: {
join: {},
},
};
syncResponse2.rooms.join[ROOM_ID] = {
timeline: {
events: [messageEncrypted],
},
};
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse2);
return aliceTestClient.httpBackend.flush("/sync", 2).then(function() {
return aliceTestClient.httpBackend.flush("/sync", 2);
}).then(function() {
var room = aliceTestClient.client.getRoom(ROOM_ID);
var event = room.getLiveTimeline().getEvents()[0];
expect(event.getContent().body).toEqual('42');
@@ -403,54 +507,30 @@ describe("megolm", function() {
});
it('Alice sends a megolm message', function(done) {
// establish an olm session with alice
var p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
var p2pSession;
var olmEvent = encryptOlmEvent({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
});
return aliceTestClient.start().then(function() {
var syncResponse = getSyncResponse(['@bob:xyz']);
var syncResponse = {
next_batch: 1,
to_device: {
events: [olmEvent],
},
rooms: {
join: {},
},
};
syncResponse.rooms.join[ROOM_ID] = {
state: {
events: [
test_utils.mkEvent({
type: 'm.room.encryption',
skey: '',
content: {
algorithm: 'm.megolm.v1.aes-sha2',
},
}),
test_utils.mkMembership({
mship: 'join',
sender: '@bob:xyz',
}),
],
},
};
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
// establish an olm session with alice
p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
var inboundGroupSession;
return aliceTestClient.httpBackend.flush('/sync', 1).then(function() {
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(200, {
device_keys: {
'@bob:xyz': {
'DEVICE_ID': testDeviceKeys,
},
}
var olmEvent = encryptOlmEvent({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
});
syncResponse.to_device = { events: [olmEvent] };
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.httpBackend.flush('/sync', 1);
}).then(function() {
var inboundGroupSession;
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz')
);
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/'
).respond(200, function(path, content) {
@@ -468,9 +548,11 @@ describe("megolm", function() {
'PUT', '/send/'
).respond(200, function(path, content) {
var ct = content.ciphertext;
var decrypted = JSON.parse(inboundGroupSession.decrypt(ct));
var r = inboundGroupSession.decrypt(ct);
console.log('Decrypted received megolm message', r);
console.log('Decrypted received megolm message', decrypted);
expect(r.message_index).toEqual(0);
var decrypted = JSON.parse(r.plaintext);
expect(decrypted.type).toEqual('m.room.message');
expect(decrypted.content.body).toEqual('test');
@@ -487,32 +569,12 @@ describe("megolm", function() {
});
it("Alice shouldn't do a second /query for non-e2e-capable devices", function(done) {
var syncResponse = {
next_batch: 1,
rooms: {
join: {},
},
};
syncResponse.rooms.join[ROOM_ID] = {
state: {
events: [
test_utils.mkEvent({
type: 'm.room.encryption',
skey: '',
content: {
algorithm: 'm.megolm.v1.aes-sha2',
},
}),
test_utils.mkMembership({
mship: 'join',
sender: '@bob:xyz',
}),
],
},
};
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.start().then(function() {
var syncResponse = getSyncResponse(['@bob:xyz']);
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.httpBackend.flush('/sync', 1).then(function() {
return aliceTestClient.httpBackend.flush('/sync', 1);
}).then(function() {
console.log("Forcing alice to download our device keys");
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(200, {
@@ -543,54 +605,28 @@ describe("megolm", function() {
it("We shouldn't attempt to send to blocked devices", function(done) {
// establish an olm session with alice
var p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
return aliceTestClient.start().then(function() {
var syncResponse = getSyncResponse(['@bob:xyz']);
var olmEvent = encryptOlmEvent({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
});
// establish an olm session with alice
var p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
var syncResponse = {
next_batch: 1,
to_device: {
events: [olmEvent],
},
rooms: {
join: {},
},
};
var olmEvent = encryptOlmEvent({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
});
syncResponse.rooms.join[ROOM_ID] = {
state: {
events: [
test_utils.mkEvent({
type: 'm.room.encryption',
skey: '',
content: {
algorithm: 'm.megolm.v1.aes-sha2',
},
}),
test_utils.mkMembership({
mship: 'join',
sender: '@bob:xyz',
}),
],
},
};
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
syncResponse.to_device = { events: [olmEvent] };
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.httpBackend.flush('/sync', 1).then(function() {
return aliceTestClient.httpBackend.flush('/sync', 1);
}).then(function() {
console.log('Forcing alice to download our device keys');
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(200, {
device_keys: {
'@bob:xyz': {
'DEVICE_ID': testDeviceKeys,
},
}
});
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz')
);
return q.all([
aliceTestClient.client.downloadKeys(['@bob:xyz']),
@@ -614,4 +650,244 @@ describe("megolm", function() {
}).nodeify(done);
});
it("We should start a new megolm session when a device is blocked", function(done) {
var p2pSession;
var megolmSessionId;
return aliceTestClient.start().then(function() {
var syncResponse = getSyncResponse(['@bob:xyz']);
// establish an olm session with alice
p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
var olmEvent = encryptOlmEvent({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
});
syncResponse.to_device = { events: [olmEvent] };
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.httpBackend.flush('/sync', 1);
}).then(function() {
console.log('Telling alice to send a megolm message');
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz')
);
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/'
).respond(200, function(path, content) {
console.log('sendToDevice: ', content);
var m = content.messages['@bob:xyz'].DEVICE_ID;
var ct = m.ciphertext[testSenderKey];
expect(ct.type).toEqual(1); // normal message
var decrypted = JSON.parse(p2pSession.decrypt(ct.type, ct.body));
console.log('decrypted sendToDevice:', decrypted);
expect(decrypted.type).toEqual('m.room_key');
megolmSessionId = decrypted.content.session_id;
return {};
});
aliceTestClient.httpBackend.when(
'PUT', '/send/'
).respond(200, function(path, content) {
console.log('/send:', content);
expect(content.session_id).toEqual(megolmSessionId);
return {
event_id: '$event_id',
};
});
return q.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
aliceTestClient.httpBackend.flush(),
]);
}).then(function() {
console.log('Telling alice to block our device');
aliceTestClient.client.setDeviceBlocked('@bob:xyz', 'DEVICE_ID');
console.log('Telling alice to send another megolm message');
aliceTestClient.httpBackend.when(
'PUT', '/send/'
).respond(200, function(path, content) {
console.log('/send:', content);
expect(content.session_id).not.toEqual(megolmSessionId);
return {
event_id: '$event_id',
};
});
return q.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test2'),
aliceTestClient.httpBackend.flush(),
]);
}).nodeify(done);
});
// https://github.com/vector-im/riot-web/issues/2676
it("Alice should send to her other devices", function(done) {
// for this test, we make the testOlmAccount be another of Alice's devices.
// it ought to get include in messages Alice sends.
var p2pSession;
var inboundGroupSession;
var decrypted;
return aliceTestClient.start(
getTestKeysQueryResponse(aliceTestClient.userId)
).then(function() {
// an encrypted room with just alice
var syncResponse = {
next_batch: 1,
rooms: {
join: {},
},
};
syncResponse.rooms.join[ROOM_ID] = {
state: {
events: [
test_utils.mkEvent({
type: 'm.room.encryption',
skey: '',
content: {
algorithm: 'm.megolm.v1.aes-sha2',
},
}),
test_utils.mkMembership({
mship: 'join',
sender: aliceTestClient.userId,
}),
],
},
};
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.httpBackend.flush();
}).then(function() {
aliceTestClient.httpBackend.when('POST', '/keys/claim').respond(
200, function(path, content)
{
expect(content.one_time_keys[aliceTestClient.userId].DEVICE_ID)
.toEqual("signed_curve25519");
return getTestKeysClaimResponse(aliceTestClient.userId);
});
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/'
).respond(200, function(path, content) {
console.log("sendToDevice: ", content);
var m = content.messages[aliceTestClient.userId].DEVICE_ID;
var ct = m.ciphertext[testSenderKey];
expect(ct.type).toEqual(0); // pre-key message
p2pSession = new Olm.Session();
p2pSession.create_inbound(testOlmAccount, ct.body);
var decrypted = JSON.parse(p2pSession.decrypt(ct.type, ct.body));
expect(decrypted.type).toEqual('m.room_key');
inboundGroupSession = new Olm.InboundGroupSession();
inboundGroupSession.create(decrypted.content.session_key);
return {};
});
aliceTestClient.httpBackend.when(
'PUT', '/send/'
).respond(200, function(path, content) {
var ct = content.ciphertext;
var r = inboundGroupSession.decrypt(ct);
console.log('Decrypted received megolm message', r);
decrypted = JSON.parse(r.plaintext);
return {
event_id: '$event_id',
};
});
return q.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
aliceTestClient.httpBackend.flush(),
]);
}).then(function() {
expect(decrypted.type).toEqual('m.room.message');
expect(decrypted.content.body).toEqual('test');
}).nodeify(done);
});
it('Alice should wait for device list to complete when sending a megolm message',
function(done) {
var p2pSession;
var inboundGroupSession;
var downloadPromise;
var sendPromise;
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/'
).respond(200, function(path, content) {
var m = content.messages['@bob:xyz'].DEVICE_ID;
var ct = m.ciphertext[testSenderKey];
var decrypted = JSON.parse(p2pSession.decrypt(ct.type, ct.body));
expect(decrypted.type).toEqual('m.room_key');
inboundGroupSession = new Olm.InboundGroupSession();
inboundGroupSession.create(decrypted.content.session_key);
return {};
});
aliceTestClient.httpBackend.when(
'PUT', '/send/'
).respond(200, function(path, content) {
var ct = content.ciphertext;
var r = inboundGroupSession.decrypt(ct);
console.log('Decrypted received megolm message', r);
expect(r.message_index).toEqual(0);
var decrypted = JSON.parse(r.plaintext);
expect(decrypted.type).toEqual('m.room.message');
expect(decrypted.content.body).toEqual('test');
return {
event_id: '$event_id',
};
});
return aliceTestClient.start().then(function() {
var syncResponse = getSyncResponse(['@bob:xyz']);
// establish an olm session with alice
p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
var olmEvent = encryptOlmEvent({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
});
syncResponse.to_device = { events: [olmEvent] };
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.httpBackend.flush('/sync', 1);
}).then(function() {
console.log('Forcing alice to download our device keys');
// this will block
downloadPromise = aliceTestClient.client.downloadKeys(['@bob:xyz']);
}).then(function() {
// so will this.
sendPromise = aliceTestClient.client.sendTextMessage(ROOM_ID, 'test');
}).then(function() {
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz')
);
return aliceTestClient.httpBackend.flush();
}).then(function() {
return q.all([downloadPromise, sendPromise]);
}).nodeify(done);
});
});
+1 -1
View File
@@ -9,6 +9,6 @@ describe("Crypto", function() {
}
it("Crypto exposes the correct olm library version", function() {
expect(Crypto.getOlmVersion()).toEqual([1, 3, 0]);
expect(Crypto.getOlmVersion()).toEqual([2, 0, 0]);
});
});