Merge branch 'develop' into matthew/filtered-timelines
This commit is contained in:
+103
-2
@@ -1,11 +1,112 @@
|
||||
Changes in \<unreleased>
|
||||
=======================
|
||||
Changes in [0.5.6](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.5.6) (2016-08-28)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.5.5...v0.5.6)
|
||||
|
||||
* Put all of the megolm keys in one room message
|
||||
[\#182](https://github.com/matrix-org/matrix-js-sdk/pull/182)
|
||||
* Reinstate device blocking for simple Olm
|
||||
[\#181](https://github.com/matrix-org/matrix-js-sdk/pull/181)
|
||||
* support for unpacking megolm keys
|
||||
[\#180](https://github.com/matrix-org/matrix-js-sdk/pull/180)
|
||||
* Send out megolm keys when we start a megolm session
|
||||
[\#179](https://github.com/matrix-org/matrix-js-sdk/pull/179)
|
||||
* Change the result structure for ensureOlmSessionsForUsers
|
||||
[\#178](https://github.com/matrix-org/matrix-js-sdk/pull/178)
|
||||
* Factor out a function for doing olm encryption
|
||||
[\#177](https://github.com/matrix-org/matrix-js-sdk/pull/177)
|
||||
* Move DeviceInfo and DeviceVerification to separate module
|
||||
[\#175](https://github.com/matrix-org/matrix-js-sdk/pull/175)
|
||||
* Make encryption asynchronous
|
||||
[\#176](https://github.com/matrix-org/matrix-js-sdk/pull/176)
|
||||
* Added ability to set and get status_msg for presence.
|
||||
[\#167](https://github.com/matrix-org/matrix-js-sdk/pull/167)
|
||||
* Megolm: don't dereference nullable object
|
||||
[\#174](https://github.com/matrix-org/matrix-js-sdk/pull/174)
|
||||
* Implement megolm encryption/decryption
|
||||
[\#173](https://github.com/matrix-org/matrix-js-sdk/pull/173)
|
||||
* Update our push rules when they come down stream
|
||||
[\#170](https://github.com/matrix-org/matrix-js-sdk/pull/170)
|
||||
* Factor Olm encryption/decryption out to new classes
|
||||
[\#172](https://github.com/matrix-org/matrix-js-sdk/pull/172)
|
||||
* Make DeviceInfo more useful, and refactor crypto methods to use it
|
||||
[\#171](https://github.com/matrix-org/matrix-js-sdk/pull/171)
|
||||
* Move login and register methods into base-apis
|
||||
[\#169](https://github.com/matrix-org/matrix-js-sdk/pull/169)
|
||||
* Remove defaultDeviceDisplayName from MatrixClient options
|
||||
[\#168](https://github.com/matrix-org/matrix-js-sdk/pull/168)
|
||||
|
||||
Changes in [0.5.5](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.5.5) (2016-08-11)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.5.4...v0.5.5)
|
||||
|
||||
* Add room.getAliases() and room.getCanonicalAlias
|
||||
* Add API calls `/register/email/requestToken`, `/account/password/email/requestToken` and `/account/3pid/email/requestToken`
|
||||
* Add `User.currentlyActive` and `User.lastPresenceTs` events for changes in fields on the User object
|
||||
* Add `logout` and `deactivateAccount`
|
||||
|
||||
* Make sure we actually stop the sync loop on logout
|
||||
[\#166](https://github.com/matrix-org/matrix-js-sdk/pull/166)
|
||||
* Zero is a valid power level
|
||||
[\#164](https://github.com/matrix-org/matrix-js-sdk/pull/164)
|
||||
* Verify e2e keys on download
|
||||
[\#163](https://github.com/matrix-org/matrix-js-sdk/pull/163)
|
||||
* Factor crypto stuff out of MatrixClient
|
||||
[\#162](https://github.com/matrix-org/matrix-js-sdk/pull/162)
|
||||
* Refactor device key upload
|
||||
[\#161](https://github.com/matrix-org/matrix-js-sdk/pull/161)
|
||||
* Wrappers for devices API
|
||||
[\#158](https://github.com/matrix-org/matrix-js-sdk/pull/158)
|
||||
* Add deactivate account function
|
||||
[\#160](https://github.com/matrix-org/matrix-js-sdk/pull/160)
|
||||
* client.listDeviceKeys: Expose device display name
|
||||
[\#159](https://github.com/matrix-org/matrix-js-sdk/pull/159)
|
||||
* Add `logout`
|
||||
[\#157](https://github.com/matrix-org/matrix-js-sdk/pull/157)
|
||||
* Fix email registration
|
||||
[\#156](https://github.com/matrix-org/matrix-js-sdk/pull/156)
|
||||
* Factor out MatrixClient methods to MatrixBaseApis
|
||||
[\#155](https://github.com/matrix-org/matrix-js-sdk/pull/155)
|
||||
* Fix some broken tests
|
||||
[\#154](https://github.com/matrix-org/matrix-js-sdk/pull/154)
|
||||
* make jenkins fail the build if the tests fail
|
||||
[\#153](https://github.com/matrix-org/matrix-js-sdk/pull/153)
|
||||
* deviceId-related fixes
|
||||
[\#152](https://github.com/matrix-org/matrix-js-sdk/pull/152)
|
||||
* /login, /register: Add device_id and initial_device_display_name
|
||||
[\#151](https://github.com/matrix-org/matrix-js-sdk/pull/151)
|
||||
* Support global account_data
|
||||
[\#150](https://github.com/matrix-org/matrix-js-sdk/pull/150)
|
||||
* Add more events to User
|
||||
[\#149](https://github.com/matrix-org/matrix-js-sdk/pull/149)
|
||||
* Add API calls for other requestToken endpoints
|
||||
[\#148](https://github.com/matrix-org/matrix-js-sdk/pull/148)
|
||||
* Add register-specific request token endpoint
|
||||
[\#147](https://github.com/matrix-org/matrix-js-sdk/pull/147)
|
||||
* Set a valid SPDX license identifier in package.json
|
||||
[\#139](https://github.com/matrix-org/matrix-js-sdk/pull/139)
|
||||
* Configure encryption on m.room.encryption events
|
||||
[\#145](https://github.com/matrix-org/matrix-js-sdk/pull/145)
|
||||
* Implement device blocking
|
||||
[\#146](https://github.com/matrix-org/matrix-js-sdk/pull/146)
|
||||
* Clearer doc for setRoomDirectoryVisibility
|
||||
[\#144](https://github.com/matrix-org/matrix-js-sdk/pull/144)
|
||||
* crypto: use memberlist to derive recipient list
|
||||
[\#143](https://github.com/matrix-org/matrix-js-sdk/pull/143)
|
||||
* Support for marking devices as unverified
|
||||
[\#142](https://github.com/matrix-org/matrix-js-sdk/pull/142)
|
||||
* Add Olm as an optionalDependency
|
||||
[\#141](https://github.com/matrix-org/matrix-js-sdk/pull/141)
|
||||
* Add room.getAliases() and room.getCanonicalAlias()
|
||||
[\#140](https://github.com/matrix-org/matrix-js-sdk/pull/140)
|
||||
* Change how MatrixEvent manages encrypted events
|
||||
[\#138](https://github.com/matrix-org/matrix-js-sdk/pull/138)
|
||||
* Catch exceptions when encrypting events
|
||||
[\#137](https://github.com/matrix-org/matrix-js-sdk/pull/137)
|
||||
* Support for marking devices as verified
|
||||
[\#136](https://github.com/matrix-org/matrix-js-sdk/pull/136)
|
||||
* Various matrix-client refactorings and fixes
|
||||
[\#134](https://github.com/matrix-org/matrix-js-sdk/pull/134)
|
||||
|
||||
Changes in [0.5.4](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.5.4) (2016-06-02)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.5.3...v0.5.4)
|
||||
|
||||
Vendored
+37345
File diff suppressed because one or more lines are too long
+38
File diff suppressed because one or more lines are too long
Vendored
+41222
File diff suppressed because one or more lines are too long
+38
File diff suppressed because one or more lines are too long
@@ -15,6 +15,12 @@ limitations under the License.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* olm.js wrapper
|
||||
*
|
||||
* @module OlmDevice
|
||||
*/
|
||||
|
||||
var Olm = require("olm");
|
||||
var utils = require("./utils");
|
||||
|
||||
@@ -25,6 +31,8 @@ var utils = require("./utils");
|
||||
* Accounts and sessions are kept pickled in a sessionStore.
|
||||
*
|
||||
* @constructor
|
||||
* @alias module:OlmDevice
|
||||
*
|
||||
* @param {Object} sessionStore A store to be used for data in end-to-end
|
||||
* crypto
|
||||
*
|
||||
@@ -53,6 +61,10 @@ function OlmDevice(sessionStore) {
|
||||
|
||||
this.deviceCurve25519Key = e2eKeys.curve25519;
|
||||
this.deviceEd25519Key = e2eKeys.ed25519;
|
||||
|
||||
// we don't bother stashing outboundgroupsessions in the sessionstore -
|
||||
// instead we keep them here.
|
||||
this._outboundGroupSessionStore = {};
|
||||
}
|
||||
|
||||
|
||||
@@ -282,6 +294,22 @@ OlmDevice.prototype.getSessionIdsForDevice = function(theirDeviceIdentityKey) {
|
||||
return utils.keys(sessions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the right olm session id for encrypting messages to the given identity key
|
||||
*
|
||||
* @param {string} theirDeviceIdentityKey Curve25519 identity key for the
|
||||
* remote device
|
||||
* @return {string?} session id, or null if no established session
|
||||
*/
|
||||
OlmDevice.prototype.getSessionIdForDevice = function(theirDeviceIdentityKey) {
|
||||
var sessionIds = this.getSessionIdsForDevice(theirDeviceIdentityKey);
|
||||
if (sessionIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// Use the session with the lowest ID.
|
||||
sessionIds.sort();
|
||||
return sessionIds[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypt an outgoing message using an existing session
|
||||
@@ -350,6 +378,219 @@ OlmDevice.prototype.decryptMessage = function(
|
||||
};
|
||||
|
||||
|
||||
// Outbound group session
|
||||
// ======================
|
||||
|
||||
/**
|
||||
* store an OutboundGroupSession in _outboundGroupSessionStore
|
||||
*
|
||||
* @param {Olm.OutboundGroupSession} session
|
||||
* @private
|
||||
*/
|
||||
OlmDevice.prototype._saveOutboundGroupSession = function(session) {
|
||||
var pickledSession = session.pickle(this._pickleKey);
|
||||
this._outboundGroupSessionStore[session.session_id()] = pickledSession;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* extract an OutboundGroupSession from _outboundGroupSessionStore and call the
|
||||
* given function
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* @param {function} func
|
||||
* @return {object} result of func
|
||||
* @private
|
||||
*/
|
||||
OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) {
|
||||
var pickled = this._outboundGroupSessionStore[sessionId];
|
||||
if (pickled === null) {
|
||||
throw new Error("Unknown outbound group session " + sessionId);
|
||||
}
|
||||
|
||||
var session = new Olm.OutboundGroupSession();
|
||||
try {
|
||||
session.unpickle(this._pickleKey, pickled);
|
||||
return func(session);
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generate a new outbound group session
|
||||
*
|
||||
* @return {string} sessionId for the outbound session.
|
||||
*/
|
||||
OlmDevice.prototype.createOutboundGroupSession = function() {
|
||||
var session = new Olm.OutboundGroupSession();
|
||||
try {
|
||||
session.create();
|
||||
this._saveOutboundGroupSession(session);
|
||||
return session.session_id();
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt an outgoing message with an outbound group session
|
||||
*
|
||||
* @param {string} sessionId the id of the outboundgroupsession
|
||||
* @param {string} payloadString payload to be encrypted and sent
|
||||
*
|
||||
* @return {string} ciphertext
|
||||
*/
|
||||
OlmDevice.prototype.encryptGroupMessage = function(sessionId, payloadString) {
|
||||
var self = this;
|
||||
|
||||
return this._getOutboundGroupSession(sessionId, function(session) {
|
||||
var res = session.encrypt(payloadString);
|
||||
self._saveOutboundGroupSession(session);
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the session keys for an outbound group session
|
||||
*
|
||||
* @param {string} sessionId the id of the outbound group session
|
||||
*
|
||||
* @return {{chain_index: number, key: string}} current chain index, and
|
||||
* base64-encoded secret key.
|
||||
*/
|
||||
OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) {
|
||||
return this._getOutboundGroupSession(sessionId, function(session) {
|
||||
return {
|
||||
chain_index: session.message_index(),
|
||||
key: session.session_key(),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Inbound group session
|
||||
// =====================
|
||||
|
||||
/**
|
||||
* store an InboundGroupSession in the session store
|
||||
*
|
||||
* @param {string} roomId
|
||||
* @param {string} senderKey
|
||||
* @param {string} sessionId
|
||||
* @param {Olm.InboundGroupSession} session
|
||||
* @private
|
||||
*/
|
||||
OlmDevice.prototype._saveInboundGroupSession = function(
|
||||
roomId, senderKey, sessionId, session
|
||||
) {
|
||||
var r = {
|
||||
room_id: roomId,
|
||||
session: session.pickle(this._pickleKey),
|
||||
};
|
||||
|
||||
this._sessionStore.storeEndToEndInboundGroupSession(
|
||||
senderKey, sessionId, JSON.stringify(r)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* extract an InboundGroupSession from the session store and call the given function
|
||||
*
|
||||
* @param {string} roomId
|
||||
* @param {string} senderKey
|
||||
* @param {string} sessionId
|
||||
* @param {function} func
|
||||
* @return {object} result of func
|
||||
* @private
|
||||
*/
|
||||
OlmDevice.prototype._getInboundGroupSession = function(
|
||||
roomId, senderKey, sessionId, func
|
||||
) {
|
||||
var r = this._sessionStore.getEndToEndInboundGroupSession(
|
||||
senderKey, sessionId
|
||||
);
|
||||
|
||||
if (r === null) {
|
||||
throw new Error("Unknown inbound group session id");
|
||||
}
|
||||
|
||||
r = JSON.parse(r);
|
||||
|
||||
// check that the room id matches the original one for the session. This stops
|
||||
// the HS pretending a message was targeting a different room.
|
||||
if (roomId !== r.room_id) {
|
||||
throw new Error(
|
||||
"Mismatched room_id for inbound group session (expected " + r.room_id +
|
||||
", was " + roomId + ")"
|
||||
);
|
||||
}
|
||||
|
||||
var session = new Olm.InboundGroupSession();
|
||||
try {
|
||||
session.unpickle(this._pickleKey, r.session);
|
||||
return func(session);
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an inbound group session to the session store
|
||||
*
|
||||
* @param {string} roomId room in which this session will be used
|
||||
* @param {string} senderKey base64-encoded curve25519 key of the sender
|
||||
* @param {string} sessionId session identifier
|
||||
* @param {string} sessionKey base64-encoded secret key at index chainIndex
|
||||
* @param {number} chainIndex index at which sessionKey applies
|
||||
*/
|
||||
OlmDevice.prototype.addInboundGroupSession = function(
|
||||
roomId, senderKey, sessionId, sessionKey, chainIndex
|
||||
) {
|
||||
var self = this;
|
||||
var session = new Olm.InboundGroupSession();
|
||||
try {
|
||||
session.create(chainIndex, sessionKey);
|
||||
self._saveInboundGroupSession(roomId, senderKey, sessionId, session);
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt a received message with an inbound group session
|
||||
*
|
||||
* @param {string} roomId room in which the message was received
|
||||
* @param {string} senderKey base64-encoded curve25519 key of the sender
|
||||
* @param {string} sessionId session identifier
|
||||
* @param {string} body base64-encoded body of the encrypted message
|
||||
*
|
||||
* @return {string} plaintext
|
||||
*/
|
||||
OlmDevice.prototype.decryptGroupMessage = function(
|
||||
roomId, senderKey, sessionId, body
|
||||
) {
|
||||
var self = this;
|
||||
|
||||
function decrypt(session) {
|
||||
var res = session.decrypt(body);
|
||||
self._saveInboundGroupSession(
|
||||
roomId, senderKey, sessionId, session
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
return this._getInboundGroupSession(
|
||||
roomId, senderKey, sessionId, decrypt
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Utilities
|
||||
// =========
|
||||
|
||||
/**
|
||||
* Verify an ed25519 signature.
|
||||
*
|
||||
|
||||
@@ -104,6 +104,66 @@ MatrixBaseApis.prototype.isLoggedIn = function() {
|
||||
// Registration/Login operations
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @param {string} sessionId
|
||||
* @param {Object} auth
|
||||
* @param {boolean} bindEmail
|
||||
* @param {string} guestAccessToken
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixBaseApis.prototype.register = function(
|
||||
username, password,
|
||||
sessionId, auth, bindEmail, guestAccessToken,
|
||||
callback
|
||||
) {
|
||||
if (auth === undefined) { auth = {}; }
|
||||
if (sessionId) { auth.session = sessionId; }
|
||||
|
||||
var params = {
|
||||
auth: auth
|
||||
};
|
||||
if (username !== undefined) { params.username = username; }
|
||||
if (password !== undefined) { params.password = password; }
|
||||
if (bindEmail !== undefined) { params.bind_email = bindEmail; }
|
||||
if (guestAccessToken !== undefined) { params.guest_access_token = guestAccessToken; }
|
||||
|
||||
return this.registerRequest(params, undefined, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a guest account.
|
||||
* @param {Object=} opts Registration options
|
||||
* @param {Object} opts.body JSON HTTP body to provide.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixBaseApis.prototype.registerGuest = function(opts, callback) {
|
||||
opts = opts || {};
|
||||
opts.body = opts.body || {};
|
||||
return this.registerRequest(opts.body, "guest", callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Object} data parameters for registration request
|
||||
* @param {string=} kind type of user to register. may be "guest"
|
||||
* @param {module:client.callback=} callback
|
||||
* @return {module:client.Promise} Resolves: to the /register response
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixBaseApis.prototype.registerRequest = function(data, kind, callback) {
|
||||
var params = {};
|
||||
if (kind) { params.kind = kind; }
|
||||
|
||||
return this._http.request(
|
||||
callback, "POST", "/register", params, data
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
@@ -113,6 +173,76 @@ MatrixBaseApis.prototype.loginFlows = function(callback) {
|
||||
return this._http.request(callback, "GET", "/login");
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} loginType
|
||||
* @param {Object} data
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixBaseApis.prototype.login = function(loginType, data, callback) {
|
||||
var login_data = {
|
||||
type: loginType,
|
||||
};
|
||||
|
||||
// merge data into login_data
|
||||
utils.extend(login_data, data);
|
||||
|
||||
return this._http.authedRequest(
|
||||
callback, "POST", "/login", undefined, login_data
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixBaseApis.prototype.loginWithPassword = function(user, password, callback) {
|
||||
return this.login("m.login.password", {
|
||||
user: user,
|
||||
password: password
|
||||
}, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} relayState URL Callback after SAML2 Authentication
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixBaseApis.prototype.loginWithSAML2 = function(relayState, callback) {
|
||||
return this.login("m.login.saml2", {
|
||||
relay_state: relayState
|
||||
}, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} redirectUrl The URL to redirect to after the HS
|
||||
* authenticates with CAS.
|
||||
* @return {string} The HS URL to hit to begin the CAS login process.
|
||||
*/
|
||||
MatrixBaseApis.prototype.getCasLoginUrl = function(redirectUrl) {
|
||||
return this._http.getUrl("/login/cas/redirect", {
|
||||
"redirectUrl": redirectUrl
|
||||
}, httpApi.PREFIX_UNSTABLE);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} token Login token previously received from homeserver
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixBaseApis.prototype.loginWithToken = function(token, callback) {
|
||||
return this.login("m.login.token", {
|
||||
token: token
|
||||
}, callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs out the current session.
|
||||
* Obviously, further calls that require authorisation should fail after this
|
||||
|
||||
+58
-155
@@ -78,9 +78,6 @@ try {
|
||||
* tracking things like crypto keys and access tokens. If not specified,
|
||||
* end-to-end crypto will be disabled.
|
||||
*
|
||||
* @param {string=} opts.defaultDeviceDisplayName Initial display name to set
|
||||
* on new devices
|
||||
*
|
||||
* @param {Object=} opts.sessionStore A store to be used for end-to-end crypto
|
||||
* session data. This should be a {@link
|
||||
* module:store/session/webstorage~WebStorageSessionStore|WebStorageSessionStore},
|
||||
@@ -109,7 +106,6 @@ function MatrixClient(opts) {
|
||||
this.store = opts.store || new StubStore();
|
||||
|
||||
this.deviceId = opts.deviceId || null;
|
||||
this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName || "js-sdk device";
|
||||
|
||||
var userId = (opts.userId || null);
|
||||
this.credentials = {
|
||||
@@ -381,14 +377,13 @@ MatrixClient.prototype.isEventSenderVerified = function(event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var cryptoContent = event.getWireContent();
|
||||
var sender_key = cryptoContent.sender_key;
|
||||
var sender_key = event.getSenderKey();
|
||||
|
||||
if (!sender_key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var algorithm = cryptoContent.algorithm;
|
||||
var algorithm = event.getWireContent().algorithm;
|
||||
|
||||
return this._crypto.isSenderKeyVerified(
|
||||
event.getSender(), algorithm, sender_key
|
||||
@@ -422,6 +417,20 @@ function onCryptoEvent(client, event) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handle a room key event
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {MatrixEvent} event
|
||||
*/
|
||||
MatrixClient.prototype._onRoomKeyEvent = function(event) {
|
||||
if (!this._crypto) {
|
||||
return;
|
||||
}
|
||||
this._crypto.onRoomKeyEvent(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable end-to-end encryption for a room.
|
||||
* @param {string} roomId The room ID to enable encryption in.
|
||||
@@ -826,10 +835,18 @@ function _sendEvent(client, room, event, callback) {
|
||||
// so that we can handle synchronous and asynchronous exceptions with the
|
||||
// same code path.
|
||||
return q().then(function() {
|
||||
var encryptionPromise = null;
|
||||
if (client._crypto) {
|
||||
client._crypto.encryptEventIfNeeded(event, room);
|
||||
encryptionPromise = client._crypto.encryptEventIfNeeded(event, room);
|
||||
}
|
||||
|
||||
if (encryptionPromise) {
|
||||
_updatePendingEventStatus(room, event, EventStatus.ENCRYPTING);
|
||||
encryptionPromise = encryptionPromise.then(function() {
|
||||
_updatePendingEventStatus(room, event, EventStatus.SENDING);
|
||||
});
|
||||
}
|
||||
return encryptionPromise;
|
||||
}).then(function() {
|
||||
var promise;
|
||||
// this event may be queued
|
||||
if (client.scheduler) {
|
||||
@@ -861,10 +878,14 @@ function _sendEvent(client, room, event, callback) {
|
||||
// the request failed to send.
|
||||
console.error("Error sending event", err.stack || err);
|
||||
|
||||
_updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
|
||||
try {
|
||||
_updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
|
||||
|
||||
if (callback) {
|
||||
callback(err);
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
} catch (err2) {
|
||||
console.error("Exception in error handler!", err2.stack || err);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
@@ -1417,25 +1438,29 @@ MatrixClient.prototype.mxcUrlToHttp =
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} presence
|
||||
* @param {Object} opts Options to apply
|
||||
* @param {string} opts.presence One of "online", "offline" or "unavailable"
|
||||
* @param {string} opts.status_msg The status message to attach.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
* @throws If 'presence' isn't a valid presence enum value.
|
||||
*/
|
||||
MatrixClient.prototype.setPresence = function(presence, callback) {
|
||||
MatrixClient.prototype.setPresence = function(opts, callback) {
|
||||
var path = utils.encodeUri("/presence/$userId/status", {
|
||||
$userId: this.credentials.userId
|
||||
});
|
||||
var validStates = ["offline", "online", "unavailable"];
|
||||
if (validStates.indexOf(presence) == -1) {
|
||||
throw new Error("Bad presence value: " + presence);
|
||||
|
||||
if (typeof opts === "string") {
|
||||
opts = { presence: opts };
|
||||
}
|
||||
|
||||
var validStates = ["offline", "online", "unavailable"];
|
||||
if (validStates.indexOf(opts.presence) == -1) {
|
||||
throw new Error("Bad presence value: " + opts.presence);
|
||||
}
|
||||
var content = {
|
||||
presence: presence
|
||||
};
|
||||
return this._http.authedRequest(
|
||||
callback, "PUT", path, undefined, content
|
||||
callback, "PUT", path, undefined, opts
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1737,47 +1762,6 @@ MatrixClient.prototype.paginateEventTimeline = function(eventTimeline, opts) {
|
||||
return promise;
|
||||
};
|
||||
|
||||
// Registration/Login operations
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* @param {string} loginType
|
||||
* @param {Object} data
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.login = function(loginType, data, callback) {
|
||||
var login_data = {
|
||||
type: loginType,
|
||||
device_id: this.deviceId,
|
||||
initial_device_display_name: this.defaultDeviceDisplayName,
|
||||
};
|
||||
|
||||
// merge data into login_data
|
||||
for (var k in data) {
|
||||
if (data.hasOwnProperty(k)) { login_data[k] = data[k]; }
|
||||
}
|
||||
|
||||
return this._http.authedRequest(
|
||||
callback, "POST", "/login", undefined, login_data
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a guest account.
|
||||
* @param {Object=} opts Registration options
|
||||
* @param {Object} opts.body JSON HTTP body to provide.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.registerGuest = function(opts, callback) {
|
||||
opts = opts || {};
|
||||
opts.body = opts.body || {};
|
||||
return this.registerRequest(opts.body, "guest", callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Peek into a room and receive updates about the room. This only works if the
|
||||
* history visibility for the room is world_readable.
|
||||
@@ -1831,49 +1815,8 @@ MatrixClient.prototype.setGuestAccess = function(roomId, opts) {
|
||||
return q.all(readPromise, writePromise);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @param {string} sessionId
|
||||
* @param {Object} auth
|
||||
* @param {boolean} bindEmail
|
||||
* @param {string} guestAccessToken
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.register = function(username, password,
|
||||
sessionId, auth, bindEmail, guestAccessToken,
|
||||
callback) {
|
||||
if (auth === undefined) { auth = {}; }
|
||||
if (sessionId) { auth.session = sessionId; }
|
||||
|
||||
var params = {
|
||||
auth: auth
|
||||
};
|
||||
if (username !== undefined) { params.username = username; }
|
||||
if (password !== undefined) { params.password = password; }
|
||||
if (bindEmail !== undefined) { params.bind_email = bindEmail; }
|
||||
if (guestAccessToken !== undefined) { params.guest_access_token = guestAccessToken; }
|
||||
|
||||
return this.registerRequest(params, undefined, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Object} data parameters for registration request
|
||||
* @param {string=} kind type of user to register. may be "guest"
|
||||
* @param {module:client.callback=} callback
|
||||
* @return {module:client.Promise} Resolves: to the /register response
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.registerRequest = function(data, kind, callback) {
|
||||
var params = {};
|
||||
if (kind) { params.kind = kind; }
|
||||
|
||||
return this._http.request(
|
||||
callback, "POST", "/register", params, data
|
||||
);
|
||||
};
|
||||
// Registration/Login operations
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* Requests an email verification token for the purposes of registration.
|
||||
@@ -1991,54 +1934,6 @@ MatrixClient.prototype._requestTokenFromEndpoint = function(endpoint,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.loginWithPassword = function(user, password, callback) {
|
||||
return this.login("m.login.password", {
|
||||
user: user,
|
||||
password: password
|
||||
}, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} relayState URL Callback after SAML2 Authentication
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.loginWithSAML2 = function(relayState, callback) {
|
||||
return this.login("m.login.saml2", {
|
||||
relay_state: relayState
|
||||
}, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} redirectUrl The URL to redirect to after the HS
|
||||
* authenticates with CAS.
|
||||
* @return {string} The HS URL to hit to begin the CAS login process.
|
||||
*/
|
||||
MatrixClient.prototype.getCasLoginUrl = function(redirectUrl) {
|
||||
return this._http.getUrl("/login/cas/redirect", {
|
||||
"redirectUrl": redirectUrl
|
||||
}, httpApi.PREFIX_UNSTABLE);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} token Login token previously received from homeserver
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.loginWithToken = function(token, callback) {
|
||||
return this.login("m.login.token", {
|
||||
token: token
|
||||
}, callback);
|
||||
};
|
||||
|
||||
// Push operations
|
||||
// ===============
|
||||
@@ -2449,7 +2344,7 @@ MatrixClient.prototype.getTurnServers = function() {
|
||||
* 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
|
||||
* "<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".
|
||||
*
|
||||
* @param {Number=} opts.pollTimeout The number of milliseconds to wait on /events.
|
||||
@@ -2746,7 +2641,15 @@ function _PojoToMatrixEventMapper(client) {
|
||||
if (plainOldJsObject.type === "m.room.encrypted") {
|
||||
clearData = _decryptMessage(client, plainOldJsObject);
|
||||
}
|
||||
return new MatrixEvent(plainOldJsObject, clearData);
|
||||
var matrixEvent = new MatrixEvent(plainOldJsObject, clearData);
|
||||
|
||||
// XXXX massive hack to deal with the fact that megolm keys are in the
|
||||
// room for now, and we need to handle them before attempting to
|
||||
// decrypt the following megolm messages.
|
||||
if (matrixEvent.getType() == "m.room_key") {
|
||||
client._onRoomKeyEvent(matrixEvent);
|
||||
}
|
||||
return matrixEvent;
|
||||
}
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
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 q = require("q");
|
||||
|
||||
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
|
||||
* @alias module:crypto-algorithms/base.EncryptionAlgorithm
|
||||
*
|
||||
* @param {object} params parameters
|
||||
* @param {string} params.deviceId The identifier for this device.
|
||||
* @param {module:crypto} params.crypto crypto core
|
||||
* @param {module:OlmDevice} params.olmDevice olm.js wrapper
|
||||
* @param {module:base-apis~MatrixBaseApis} baseApis base matrix api interface
|
||||
* @param {string} params.roomId The ID of the room we will be sending to
|
||||
*/
|
||||
var EncryptionAlgorithm = function(params) {
|
||||
this._deviceId = params.deviceId;
|
||||
this._crypto = params.crypto;
|
||||
this._olmDevice = params.olmDevice;
|
||||
this._baseApis = params.baseApis;
|
||||
this._roomId = params.roomId;
|
||||
};
|
||||
/** */
|
||||
module.exports.EncryptionAlgorithm = EncryptionAlgorithm;
|
||||
|
||||
/**
|
||||
* Initialise this EncryptionAlgorithm instance for a particular room.
|
||||
*
|
||||
* <p>This will be called once per EncryptionAlgorithm, just after the
|
||||
* constructor is called.
|
||||
*
|
||||
* @param {string[]} roomMembers list of currently-joined users in the room
|
||||
* @return {module:client.Promise} Promise which resolves when setup is complete
|
||||
*/
|
||||
EncryptionAlgorithm.prototype.initRoomEncryption = function(roomMembers) {
|
||||
return q();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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 {module:client.Promise} Promise which resolves to the new event body
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* base type for decryption implementations
|
||||
*
|
||||
* @constructor
|
||||
* @alias module:crypto-algorithms/base.DecryptionAlgorithm
|
||||
*
|
||||
* @param {object} params parameters
|
||||
* @param {module:OlmDevice} params.olmDevice olm.js wrapper
|
||||
*/
|
||||
var DecryptionAlgorithm = function(params) {
|
||||
this._olmDevice = params.olmDevice;
|
||||
};
|
||||
/** */
|
||||
module.exports.DecryptionAlgorithm = DecryptionAlgorithm;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handle a key event
|
||||
*
|
||||
* @method module:crypto-algorithms/base.DecryptionAlgorithm#onRoomKeyEvent
|
||||
*
|
||||
* @param {module:modules/event~MatrixEvent} event key event
|
||||
*/
|
||||
DecryptionAlgorithm.prototype.onRoomKeyEvent = function(params) {
|
||||
// ignore by default
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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");
|
||||
require("./megolm");
|
||||
|
||||
/**
|
||||
* @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;
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
Copyright 2015, 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/megolm
|
||||
*/
|
||||
|
||||
var q = require("q");
|
||||
|
||||
var utils = require("../utils");
|
||||
var olmlib = require("../olmlib");
|
||||
var base = require("./base");
|
||||
|
||||
/**
|
||||
* Megolm encryption implementation
|
||||
*
|
||||
* @constructor
|
||||
* @extends {module:crypto-algorithms/base.EncryptionAlgorithm}
|
||||
*
|
||||
* @param {object} params parameters, as per
|
||||
* {@link module:crypto-algorithms/base.EncryptionAlgorithm}
|
||||
*/
|
||||
function MegolmEncryption(params) {
|
||||
base.EncryptionAlgorithm.call(this, params);
|
||||
this._prepPromise = null;
|
||||
this._outboundSessionId = null;
|
||||
}
|
||||
utils.inherits(MegolmEncryption, base.EncryptionAlgorithm);
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param {module:models/room} room
|
||||
*
|
||||
* @return {module:client.Promise} Promise which resolves when setup is
|
||||
* complete.
|
||||
*/
|
||||
MegolmEncryption.prototype._ensureOutboundSession = function(room) {
|
||||
if (this._prepPromise) {
|
||||
// prep already in progress
|
||||
return this._prepPromise;
|
||||
}
|
||||
|
||||
if (this._outboundSessionId) {
|
||||
// prep already done
|
||||
return q();
|
||||
}
|
||||
|
||||
var session_id = this._olmDevice.createOutboundGroupSession();
|
||||
var key = this._olmDevice.getOutboundGroupSessionKey(session_id);
|
||||
|
||||
this._olmDevice.addInboundGroupSession(
|
||||
this._roomId, this._olmDevice.deviceCurve25519Key, session_id,
|
||||
key.key, key.chain_index
|
||||
);
|
||||
|
||||
// send the keys to each (unblocked) device in the room.
|
||||
var payload = {
|
||||
type: "m.room_key",
|
||||
content: {
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
room_id: this._roomId,
|
||||
session_id: session_id,
|
||||
session_key: key.key,
|
||||
chain_index: key.chain_index,
|
||||
}
|
||||
};
|
||||
|
||||
var roomMembers = utils.map(room.getJoinedMembers(), function(u) {
|
||||
return u.userId;
|
||||
});
|
||||
|
||||
var self = this;
|
||||
|
||||
// TODO: we need to give the user a chance to block any devices or users
|
||||
// before we send them the keys; it's too late to download them here.
|
||||
this._prepPromise = this._crypto.downloadKeys(
|
||||
roomMembers, false
|
||||
).then(function(res) {
|
||||
return self._crypto.ensureOlmSessionsForUsers(roomMembers);
|
||||
}).then(function(devicemap) {
|
||||
// TODO: send OOB messages. for now, send an in-band message. Each
|
||||
// encrypted copy of the key takes up about 1K, so we'll only manage
|
||||
// about 60 copies before we hit the event size limit; but ultimately the
|
||||
// OOB messaging API will solve that problem for us.
|
||||
|
||||
var participantKeys = [];
|
||||
for (var userId in devicemap) {
|
||||
if (!devicemap.hasOwnProperty(userId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var devices = devicemap[userId];
|
||||
|
||||
for (var deviceId in devices) {
|
||||
if (!devices.hasOwnProperty(deviceId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var deviceInfo = devices[deviceId].device;
|
||||
participantKeys.push(deviceInfo.getIdentityKey());
|
||||
}
|
||||
}
|
||||
|
||||
var encryptedContent = olmlib.encryptMessageForDevices(
|
||||
self._deviceId,
|
||||
self._olmDevice,
|
||||
participantKeys,
|
||||
payload
|
||||
);
|
||||
|
||||
var txnId = '' + (new Date().getTime());
|
||||
var path = utils.encodeUri(
|
||||
"/rooms/$roomId/send/m.room.encrypted/$txnId", {
|
||||
$roomId: self._roomId,
|
||||
$txnId: txnId,
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: retries
|
||||
return self._baseApis._http.authedRequest(
|
||||
undefined, "PUT", path, undefined, encryptedContent
|
||||
);
|
||||
}).then(function() {
|
||||
// don't set this until the keys are sent successfully; if we get an
|
||||
// error, the user can restart by resending the message.
|
||||
self._outboundSessionId = session_id;
|
||||
}).finally(function() {
|
||||
self._prepPromise = null;
|
||||
});
|
||||
|
||||
return this._prepPromise;
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param {module:models/room} room
|
||||
* @param {string} eventType
|
||||
* @param {object} plaintext event content
|
||||
*
|
||||
* @return {module:client.Promise} Promise which resolves to the new event body
|
||||
*/
|
||||
MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
|
||||
var self = this;
|
||||
return this._ensureOutboundSession(room).then(function() {
|
||||
var payloadJson = {
|
||||
room_id: self._roomId,
|
||||
type: eventType,
|
||||
content: content
|
||||
};
|
||||
|
||||
var ciphertext = self._olmDevice.encryptGroupMessage(
|
||||
self._outboundSessionId, JSON.stringify(payloadJson)
|
||||
);
|
||||
|
||||
var encryptedContent = {
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
sender_key: self._olmDevice.deviceCurve25519Key,
|
||||
body: ciphertext,
|
||||
session_id: self._outboundSessionId,
|
||||
signature: "FIXME",
|
||||
};
|
||||
|
||||
return encryptedContent;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Megolm decryption implementation
|
||||
*
|
||||
* @constructor
|
||||
* @extends {module:crypto-algorithms/base.DecryptionAlgorithm}
|
||||
*
|
||||
* @param {object} params parameters, as per
|
||||
* {@link module:crypto-algorithms/base.DecryptionAlgorithm}
|
||||
*/
|
||||
function MegolmDecryption(params) {
|
||||
base.DecryptionAlgorithm.call(this, params);
|
||||
}
|
||||
utils.inherits(MegolmDecryption, 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
|
||||
*/
|
||||
MegolmDecryption.prototype.decryptEvent = function(event) {
|
||||
var content = event.content;
|
||||
|
||||
console.log("decrypting " + event.event_id + " with sid " +
|
||||
content.session_id);
|
||||
|
||||
if (!content.sender_key || !content.session_id ||
|
||||
!content.body || !content.signature
|
||||
) {
|
||||
throw new base.DecryptionError("Missing fields in input");
|
||||
}
|
||||
|
||||
try {
|
||||
var res = this._olmDevice.decryptGroupMessage(
|
||||
event.room_id, content.sender_key, content.session_id, content.body
|
||||
);
|
||||
return JSON.parse(res);
|
||||
} catch (e) {
|
||||
throw new base.DecryptionError(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param {module:modules/event~MatrixEvent} event key event
|
||||
*/
|
||||
MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
|
||||
console.log("Adding key from ", event);
|
||||
var content = event.getContent();
|
||||
|
||||
if (!content.room_id ||
|
||||
!content.session_id ||
|
||||
!content.session_key ||
|
||||
content.chain_index === undefined
|
||||
) {
|
||||
console.error("key event is missing fields");
|
||||
return;
|
||||
}
|
||||
|
||||
this._olmDevice.addInboundGroupSession(
|
||||
content.room_id, event.getSenderKey(), content.session_id,
|
||||
content.session_key, content.chain_index
|
||||
);
|
||||
};
|
||||
|
||||
base.registerAlgorithm(
|
||||
olmlib.MEGOLM_ALGORITHM, MegolmEncryption, MegolmDecryption
|
||||
);
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
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 q = require('q');
|
||||
|
||||
var utils = require("../utils");
|
||||
var olmlib = require("../olmlib");
|
||||
var DeviceInfo = require("../crypto-deviceinfo");
|
||||
var DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
|
||||
var base = require("./base");
|
||||
|
||||
/**
|
||||
* Olm encryption implementation
|
||||
*
|
||||
* @constructor
|
||||
* @extends {module:crypto-algorithms/base.EncryptionAlgorithm}
|
||||
*
|
||||
* @param {object} params parameters, as per
|
||||
* {@link module:crypto-algorithms/base.EncryptionAlgorithm}
|
||||
*/
|
||||
function OlmEncryption(params) {
|
||||
base.EncryptionAlgorithm.call(this, params);
|
||||
}
|
||||
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 {module:client.Promise} Promise which resolves to the new event body
|
||||
*/
|
||||
OlmEncryption.prototype.encryptMessage = function(room, eventType, content) {
|
||||
// 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 deviceInfo = devices[j];
|
||||
var key = deviceInfo.getIdentityKey();
|
||||
if (key == this._olmDevice.deviceCurve25519Key) {
|
||||
// don't bother setting up session to ourself
|
||||
continue;
|
||||
}
|
||||
if (deviceInfo.verified == DeviceVerification.BLOCKED) {
|
||||
// don't bother setting up sessions with blocked users
|
||||
continue;
|
||||
}
|
||||
participantKeys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return q(
|
||||
olmlib.encryptMessageForDevices(
|
||||
this._deviceId, this._olmDevice, participantKeys, {
|
||||
room_id: room.roomId,
|
||||
type: eventType,
|
||||
content: content,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Olm decryption implementation
|
||||
*
|
||||
* @constructor
|
||||
* @extends {module:crypto-algorithms/base.DecryptionAlgorithm}
|
||||
* @param {object} params parameters, as per
|
||||
* {@link module:crypto-algorithms/base.DecryptionAlgorithm}
|
||||
*/
|
||||
function OlmDecryption(params) {
|
||||
base.DecryptionAlgorithm.call(this, params);
|
||||
}
|
||||
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(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
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-deviceinfo
|
||||
*/
|
||||
|
||||
/**
|
||||
* Information about a user's device
|
||||
*
|
||||
* @constructor
|
||||
* @alias module:crypto-deviceinfo
|
||||
*
|
||||
* @property {string} deviceId the ID of this device
|
||||
*
|
||||
* @property {string[]} algorithms list of algorithms supported by this device
|
||||
*
|
||||
* @property {Object.<string,string>} keys a map from
|
||||
* <key type>:<id> -> <base64-encoded key>>
|
||||
*
|
||||
* @property {module:crypto-deviceinfo.DeviceVerification} verified
|
||||
* whether the device has been verified by the user
|
||||
*
|
||||
* @property {Object} unsigned additional data from the homeserver
|
||||
*
|
||||
* @param {string} deviceId id of the device
|
||||
*/
|
||||
function DeviceInfo(deviceId) {
|
||||
// you can't change the deviceId
|
||||
Object.defineProperty(this, 'deviceId', {
|
||||
enumerable: true,
|
||||
value: deviceId,
|
||||
});
|
||||
|
||||
this.algorithms = [];
|
||||
this.keys = {};
|
||||
this.verified = DeviceVerification.UNVERIFIED;
|
||||
this.unsigned = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* rehydrate a DeviceInfo from the session store
|
||||
*
|
||||
* @param {object} obj raw object from session store
|
||||
* @param {string} deviceId id of the device
|
||||
*
|
||||
* @return {module:crypto~DeviceInfo} new DeviceInfo
|
||||
*/
|
||||
DeviceInfo.fromStorage = function(obj, deviceId) {
|
||||
var res = new DeviceInfo(deviceId);
|
||||
for (var prop in obj) {
|
||||
if (obj.hasOwnProperty(prop)) {
|
||||
res[prop] = obj[prop];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare a DeviceInfo for JSON serialisation in the session store
|
||||
*
|
||||
* @return {object} deviceinfo with non-serialised members removed
|
||||
*/
|
||||
DeviceInfo.prototype.toStorage = function() {
|
||||
return {
|
||||
algorithms: this.algorithms,
|
||||
keys: this.keys,
|
||||
verified: this.verified,
|
||||
unsigned: this.unsigned,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the fingerprint for this device (ie, the Ed25519 key)
|
||||
*
|
||||
* @return {string} base64-encoded fingerprint of this device
|
||||
*/
|
||||
DeviceInfo.prototype.getFingerprint = function() {
|
||||
return this.keys["ed25519:" + this.deviceId];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the identity key for this device (ie, the Curve25519 key)
|
||||
*
|
||||
* @return {string} base64-encoded identity key of this device
|
||||
*/
|
||||
DeviceInfo.prototype.getIdentityKey = function() {
|
||||
return this.keys["curve25519:" + this.deviceId];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the configured display name for this device, if any
|
||||
*
|
||||
* @return {string?} displayname
|
||||
*/
|
||||
DeviceInfo.prototype.getDisplayname = function() {
|
||||
return this.unsigned.device_display_name || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @enum
|
||||
*/
|
||||
DeviceInfo.DeviceVerification = {
|
||||
VERIFIED: 1,
|
||||
UNVERIFIED: 0,
|
||||
BLOCKED: -1,
|
||||
};
|
||||
|
||||
var DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
/** */
|
||||
module.exports = DeviceInfo;
|
||||
+232
-280
@@ -15,45 +15,28 @@ limitations under the License.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
|
||||
/**
|
||||
* Internal module
|
||||
*
|
||||
* @module crypto
|
||||
*/
|
||||
|
||||
var anotherjson = require('another-json');
|
||||
var q = require("q");
|
||||
|
||||
var utils = require("./utils");
|
||||
var OlmDevice = require("./OlmDevice");
|
||||
var olmlib = require("./olmlib");
|
||||
|
||||
var OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
|
||||
var algorithms = require("./crypto-algorithms");
|
||||
|
||||
var DeviceVerification = {
|
||||
VERIFIED: 1,
|
||||
UNVERIFIED: 0,
|
||||
BLOCKED: -1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Stored information about a user's device
|
||||
*
|
||||
* @typedef {Object} DeviceInfo
|
||||
*
|
||||
* @property {string[]} altorithms list of algorithms supported by this device
|
||||
*
|
||||
* @property {Object} keys a map from <key type>:<id> -> key
|
||||
*
|
||||
* @property {DeviceVerification} verified whether the device has been
|
||||
* verified by the user
|
||||
*
|
||||
* @property {Object} unsigned additional data from the homeserver
|
||||
*/
|
||||
var DeviceInfo = require("./crypto-deviceinfo");
|
||||
var DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
/**
|
||||
* Cryptography bits
|
||||
*
|
||||
* @alias module:crypto.Crypto
|
||||
* @constructor
|
||||
* @alias module:crypto
|
||||
*
|
||||
* @param {module:base-apis~MatrixBaseApis} baseApis base matrix api interface
|
||||
*
|
||||
@@ -70,10 +53,10 @@ function Crypto(baseApis, sessionStore, userId, deviceId) {
|
||||
this._userId = userId;
|
||||
this._deviceId = deviceId;
|
||||
|
||||
this._cryptoAlgorithms = [];
|
||||
|
||||
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
|
||||
this._deviceKeys = {};
|
||||
@@ -85,7 +68,7 @@ function Crypto(baseApis, sessionStore, userId, deviceId) {
|
||||
// add our own deviceinfo to the sessionstore
|
||||
var deviceInfo = {
|
||||
keys: this._deviceKeys,
|
||||
algorithms: this._cryptoAlgorithms,
|
||||
algorithms: [olmlib.OLM_ALGORITHM],
|
||||
verified: DeviceVerification.VERIFIED,
|
||||
};
|
||||
var myDevices = this._sessionStore.getEndToEndDevicesForUser(
|
||||
@@ -138,7 +121,7 @@ function _uploadDeviceKeys(crypto) {
|
||||
var deviceId = crypto._deviceId;
|
||||
|
||||
var deviceKeys = {
|
||||
algorithms: crypto._cryptoAlgorithms,
|
||||
algorithms: [olmlib.OLM_ALGORITHM],
|
||||
device_id: deviceId,
|
||||
keys: crypto._deviceKeys,
|
||||
user_id: userId,
|
||||
@@ -187,22 +170,30 @@ function _uploadOneTimeKeys(crypto) {
|
||||
* @param {bool} forceDownload Always download the keys even if cached.
|
||||
*
|
||||
* @return {Promise} A promise which resolves to a map userId->deviceId->{@link
|
||||
* module:crypto~DeviceInfo|DeviceInfo}.
|
||||
* module:crypto-deviceinfo|DeviceInfo}.
|
||||
*/
|
||||
Crypto.prototype.downloadKeys = function(userIds, forceDownload) {
|
||||
var self = this;
|
||||
|
||||
// map from userid -> deviceid -> DeviceInfo
|
||||
var stored = {};
|
||||
|
||||
// list of userids we need to download keys for
|
||||
var downloadUsers = [];
|
||||
|
||||
for (var i = 0; i < userIds.length; ++i) {
|
||||
var userId = userIds[i];
|
||||
var devices = this._sessionStore.getEndToEndDevicesForUser(userId);
|
||||
stored[userId] = {};
|
||||
|
||||
stored[userId] = devices || {};
|
||||
if (devices && !forceDownload) {
|
||||
continue;
|
||||
var devices = this.getStoredDevicesForUser(userId);
|
||||
for (var j = 0; j < devices.length; ++j) {
|
||||
var dev = devices[j];
|
||||
stored[userId][dev.deviceId] = dev;
|
||||
}
|
||||
|
||||
if (devices.length === 0 || forceDownload) {
|
||||
downloadUsers.push(userId);
|
||||
}
|
||||
downloadUsers.push(userId);
|
||||
}
|
||||
|
||||
if (downloadUsers.length === 0) {
|
||||
@@ -218,14 +209,26 @@ Crypto.prototype.downloadKeys = function(userIds, forceDownload) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// map from deviceid -> deviceinfo for this user
|
||||
var userStore = stored[userId];
|
||||
var updated = _updateStoredDeviceKeysForUser(
|
||||
self._olmDevice, userId, userStore, res.device_keys[userId]
|
||||
);
|
||||
|
||||
if (updated) {
|
||||
if (!updated) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// update the session store
|
||||
var storage = {};
|
||||
for (var deviceId in userStore) {
|
||||
if (!userStore.hasOwnProperty(deviceId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
storage[deviceId] = userStore[deviceId].toStorage();
|
||||
self._sessionStore.storeEndToEndDevicesForUser(
|
||||
userId, userStore
|
||||
userId, storage
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -295,13 +298,12 @@ function _storeDeviceKeys(_olmDevice, userId, deviceId, userStore, deviceResult)
|
||||
return false;
|
||||
}
|
||||
|
||||
// prepare the canonical json: remove 'unsigned' and sigxsnatures, and
|
||||
// prepare the canonical json: remove 'unsigned' and signatures, and
|
||||
// stringify with anotherjson
|
||||
delete deviceResult.unsigned;
|
||||
delete deviceResult.signatures;
|
||||
var json = anotherjson.stringify(deviceResult);
|
||||
|
||||
console.log("msg:", json);
|
||||
try {
|
||||
_olmDevice.verifySignature(signKey, json, signature);
|
||||
} catch (e) {
|
||||
@@ -310,12 +312,14 @@ function _storeDeviceKeys(_olmDevice, userId, deviceId, userStore, deviceResult)
|
||||
return false;
|
||||
}
|
||||
|
||||
// DeviceInfo
|
||||
var deviceStore;
|
||||
|
||||
if (deviceId in userStore) {
|
||||
// already have this device.
|
||||
deviceStore = userStore[deviceId];
|
||||
|
||||
if (deviceStore.keys["ed25519:" + deviceId] != signKey) {
|
||||
if (deviceStore.getFingerprint() != signKey) {
|
||||
// this should only happen if the list has been MITMed; we are
|
||||
// best off sticking with the original keys.
|
||||
//
|
||||
@@ -325,9 +329,7 @@ function _storeDeviceKeys(_olmDevice, userId, deviceId, userStore, deviceResult)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
userStore[deviceId] = deviceStore = {
|
||||
verified: DeviceVerification.UNVERIFIED
|
||||
};
|
||||
userStore[deviceId] = deviceStore = new DeviceInfo(deviceId);
|
||||
}
|
||||
|
||||
deviceStore.keys = deviceResult.keys;
|
||||
@@ -337,42 +339,64 @@ function _storeDeviceKeys(_olmDevice, userId, deviceId, userStore, deviceResult)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the stored device keys for a user id
|
||||
*
|
||||
* @param {string} userId the user to list keys for.
|
||||
*
|
||||
* @return {module:crypto-deviceinfo[]} list of devices
|
||||
*/
|
||||
Crypto.prototype.getStoredDevicesForUser = function(userId) {
|
||||
var devs = this._sessionStore.getEndToEndDevicesForUser(userId);
|
||||
if (!devs) {
|
||||
return [];
|
||||
}
|
||||
var res = [];
|
||||
for (var deviceId in devs) {
|
||||
if (devs.hasOwnProperty(deviceId)) {
|
||||
res.push(DeviceInfo.fromStorage(devs[deviceId], deviceId));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* List the stored device keys for a user id
|
||||
*
|
||||
* @deprecated prefer {@link module:crypto#getStoredDevicesForUser}
|
||||
*
|
||||
* @param {string} userId the user to list keys for.
|
||||
*
|
||||
* @return {object[]} list of devices with "id", "verified", "blocked",
|
||||
* "key", and "display_name" parameters.
|
||||
*/
|
||||
Crypto.prototype.listDeviceKeys = function(userId) {
|
||||
var devices = this._sessionStore.getEndToEndDevicesForUser(userId);
|
||||
var devices = this.getStoredDevicesForUser(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,
|
||||
});
|
||||
}
|
||||
|
||||
for (var i = 0; i < devices.length; ++i) {
|
||||
var device = devices[i];
|
||||
var ed25519Key = device.getFingerprint();
|
||||
if (ed25519Key) {
|
||||
result.push({
|
||||
id: device.deviceId,
|
||||
key: ed25519Key,
|
||||
verified: Boolean(device.verified == DeviceVerification.VERIFIED),
|
||||
blocked: Boolean(device.verified == DeviceVerification.BLOCKED),
|
||||
display_name: device.getDisplayname(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// sort by deviceid
|
||||
result.sort(function(a, b) {
|
||||
if (a.deviceId < b.deviceId) { return -1; }
|
||||
if (a.deviceId > b.deviceId) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -383,10 +407,10 @@ Crypto.prototype.listDeviceKeys = function(userId) {
|
||||
* @param {string} algorithm encryption algorithm
|
||||
* @param {string} sender_key curve25519 key to match
|
||||
*
|
||||
* @return {module:crypto~DeviceInfo?}
|
||||
* @return {module:crypto-deviceinfo?}
|
||||
*/
|
||||
Crypto.prototype.getDeviceByIdentityKey = function(userId, algorithm, sender_key) {
|
||||
if (algorithm !== OLM_ALGORITHM) {
|
||||
if (algorithm !== olmlib.OLM_ALGORITHM) {
|
||||
// we only deal in olm keys
|
||||
return null;
|
||||
}
|
||||
@@ -411,7 +435,7 @@ Crypto.prototype.getDeviceByIdentityKey = function(userId, algorithm, sender_key
|
||||
}
|
||||
var deviceKey = device.keys[keyId];
|
||||
if (deviceKey == sender_key) {
|
||||
return device;
|
||||
return DeviceInfo.fromStorage(device, deviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -487,11 +511,9 @@ Crypto.prototype.isSenderKeyVerified = function(userId, algorithm, sender_key) {
|
||||
* @param {object} config The encryption config for the room.
|
||||
* @param {string[]} roomMembers userIds of room members to start sessions with
|
||||
*
|
||||
* @return {Object} A promise that will resolve when encryption is setup.
|
||||
* @return {module:client.Promise} A promise that will resolve when encryption is setup.
|
||||
*/
|
||||
Crypto.prototype.setRoomEncryption = function(roomId, config, roomMembers) {
|
||||
var self = this;
|
||||
|
||||
// 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 existingConfig = this._sessionStore.getEndToEndRoom(roomId);
|
||||
@@ -499,77 +521,107 @@ Crypto.prototype.setRoomEncryption = function(roomId, config, roomMembers) {
|
||||
if (JSON.stringify(existingConfig) != JSON.stringify(config)) {
|
||||
console.error("Ignoring m.room.encryption event which requests " +
|
||||
"a change of config in " + roomId);
|
||||
return;
|
||||
return q();
|
||||
}
|
||||
}
|
||||
|
||||
if (config.algorithm !== OLM_ALGORITHM) {
|
||||
throw new Error("Unknown algorithm: " + config.algorithm);
|
||||
var AlgClass = algorithms.ENCRYPTION_CLASSES[config.algorithm];
|
||||
if (!AlgClass) {
|
||||
throw new Error("Unable to encrypt with " + config.algorithm);
|
||||
}
|
||||
|
||||
// remove spurious keys
|
||||
config = {
|
||||
algorithm: OLM_ALGORITHM,
|
||||
algorithm: config.algorithm,
|
||||
};
|
||||
this._sessionStore.storeEndToEndRoom(roomId, config);
|
||||
|
||||
return self.downloadKeys(roomMembers, true).then(function(res) {
|
||||
return self._ensureOlmSessionsForUsers(roomMembers);
|
||||
var alg = new AlgClass({
|
||||
deviceId: this._deviceId,
|
||||
crypto: this,
|
||||
olmDevice: this._olmDevice,
|
||||
baseApis: this._baseApis,
|
||||
roomId: roomId,
|
||||
});
|
||||
this._roomAlgorithms[roomId] = alg;
|
||||
return alg.initRoomEncryption(roomMembers);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {Object} module:crypto~OlmSessionResult
|
||||
* @property {module:crypto-deviceinfo} device device info
|
||||
* @property {string?} sessionId base64 olm session id; null if no session
|
||||
* could be established
|
||||
*/
|
||||
|
||||
/**
|
||||
* Try to make sure we have established olm sessions for the given users.
|
||||
*
|
||||
* @param {string[]} users list of user ids
|
||||
*
|
||||
* @return {module:client.Promise} resolves once the sessions are complete, to
|
||||
* an object with keys <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
|
||||
* an Object mapping from userId to deviceId to
|
||||
* {@link module:crypto~OlmSessionResult}
|
||||
*/
|
||||
Crypto.prototype._ensureOlmSessionsForUsers = function(users) {
|
||||
var devicesWithoutSession = [];
|
||||
var userWithoutDevices = [];
|
||||
Crypto.prototype.ensureOlmSessionsForUsers = function(users) {
|
||||
var devicesWithoutSession = [
|
||||
// [userId, deviceId, deviceInfo], ...
|
||||
];
|
||||
var result = {};
|
||||
|
||||
for (var i = 0; i < users.length; ++i) {
|
||||
var userId = users[i];
|
||||
var devices = this._sessionStore.getEndToEndDevicesForUser(userId);
|
||||
if (!devices) {
|
||||
userWithoutDevices.push(userId);
|
||||
} else {
|
||||
for (var deviceId in devices) {
|
||||
if (devices.hasOwnProperty(deviceId)) {
|
||||
var keys = devices[deviceId];
|
||||
var key = keys.keys["curve25519:" + deviceId];
|
||||
if (key == this._olmDevice.deviceCurve25519Key) {
|
||||
continue;
|
||||
}
|
||||
if (!this._sessionStore.getEndToEndSessions(key)) {
|
||||
devicesWithoutSession.push([userId, deviceId, key]);
|
||||
}
|
||||
}
|
||||
result[userId] = {};
|
||||
|
||||
var devices = this.getStoredDevicesForUser(userId);
|
||||
for (var j = 0; j < devices.length; ++j) {
|
||||
var deviceInfo = devices[j];
|
||||
var deviceId = deviceInfo.deviceId;
|
||||
|
||||
var key = deviceInfo.getIdentityKey();
|
||||
if (key == this._olmDevice.deviceCurve25519Key) {
|
||||
// don't bother setting up session to ourself
|
||||
continue;
|
||||
}
|
||||
if (deviceInfo.verified == DeviceVerification.BLOCKED) {
|
||||
// don't bother setting up sessions with blocked users
|
||||
continue;
|
||||
}
|
||||
|
||||
var sessionId = this._olmDevice.getSessionIdForDevice(key);
|
||||
if (sessionId === null) {
|
||||
devicesWithoutSession.push([userId, deviceId, deviceInfo]);
|
||||
}
|
||||
result[userId][deviceId] = {
|
||||
device: deviceInfo,
|
||||
sessionId: sessionId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (devicesWithoutSession.length === 0) {
|
||||
return q({
|
||||
missingUsers: userWithoutDevices,
|
||||
missingDevices: []
|
||||
});
|
||||
return q(result);
|
||||
}
|
||||
|
||||
// TODO: this has a race condition - if we try to send another message
|
||||
// while we are claiming a key, we will end up claiming two and setting up
|
||||
// two sessions.
|
||||
//
|
||||
// That should eventually resolve itself, but it's poor form.
|
||||
|
||||
var self = this;
|
||||
return this._baseApis.claimOneTimeKeys(
|
||||
devicesWithoutSession
|
||||
).then(function(res) {
|
||||
var missing = {};
|
||||
for (i = 0; i < devicesWithoutSession.length; ++i) {
|
||||
for (var i = 0; i < devicesWithoutSession.length; ++i) {
|
||||
var device = devicesWithoutSession[i];
|
||||
var userRes = res.one_time_keys[device[0]] || {};
|
||||
var deviceRes = userRes[device[1]];
|
||||
var userId = device[0];
|
||||
var deviceId = device[1];
|
||||
var deviceInfo = device[2];
|
||||
|
||||
var userRes = res.one_time_keys[userId] || {};
|
||||
var deviceRes = userRes[deviceId];
|
||||
var oneTimeKey;
|
||||
for (var keyId in deviceRes) {
|
||||
if (keyId.indexOf("curve25519:") === 0) {
|
||||
@@ -578,20 +630,18 @@ Crypto.prototype._ensureOlmSessionsForUsers = function(users) {
|
||||
}
|
||||
if (oneTimeKey) {
|
||||
var sid = self._olmDevice.createOutboundSession(
|
||||
device[2], oneTimeKey
|
||||
deviceInfo.getIdentityKey(), oneTimeKey
|
||||
);
|
||||
console.log("Started new sessionid " + sid +
|
||||
" for device " + device[2]);
|
||||
" for device " + userId + ":" + deviceId);
|
||||
|
||||
result[userId][deviceId].sessionId = sid;
|
||||
} else {
|
||||
missing[device[0]] = missing[device[0]] || [];
|
||||
missing[device[0]].push([device[1]]);
|
||||
console.warn("No one-time keys for device " +
|
||||
userId + ":" + deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
missingUsers: userWithoutDevices,
|
||||
missingDevices: missing
|
||||
};
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -601,7 +651,7 @@ Crypto.prototype._ensureOlmSessionsForUsers = function(users) {
|
||||
* @return {bool} whether encryption is enabled.
|
||||
*/
|
||||
Crypto.prototype.isRoomEncrypted = function(roomId) {
|
||||
return (this._sessionStore.getEndToEndRoom(roomId) && true) || false;
|
||||
return Boolean(this._roomAlgorithms[roomId]);
|
||||
};
|
||||
|
||||
|
||||
@@ -609,131 +659,54 @@ Crypto.prototype.isRoomEncrypted = function(roomId) {
|
||||
* 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/room.Room} room destination room
|
||||
*
|
||||
* @param {module:models/room?} room destination room. Null if the destination
|
||||
* is not a room we have seen over the sync pipe.
|
||||
*
|
||||
* @return {module:client.Promise?} Promise which resolves when the event has been
|
||||
* encrypted, or null if nothing was needed
|
||||
*/
|
||||
Crypto.prototype.encryptEventIfNeeded = function(event, room) {
|
||||
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;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (event.getType() !== "m.room.message") {
|
||||
// we only encrypt m.room.message
|
||||
return;
|
||||
}
|
||||
|
||||
var roomId = event.getRoomId();
|
||||
|
||||
var e2eRoomInfo = this._sessionStore.getEndToEndRoom(roomId);
|
||||
if (!e2eRoomInfo || !e2eRoomInfo.algorithm) {
|
||||
// not encrypting messages in this room
|
||||
return;
|
||||
}
|
||||
|
||||
var encryptedContent = this._encryptMessage(
|
||||
room, e2eRoomInfo, event.getType(), event.getContent()
|
||||
);
|
||||
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);
|
||||
return null;
|
||||
}
|
||||
|
||||
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 roomId = event.getRoomId();
|
||||
|
||||
var users = utils.map(room.getJoinedMembers(), function(u) {
|
||||
return u.userId;
|
||||
var alg = this._roomAlgorithms[roomId];
|
||||
if (!alg) {
|
||||
// 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 null;
|
||||
}
|
||||
|
||||
return alg.encryptMessage(
|
||||
room, event.getType(), event.getContent()
|
||||
).then(function(encryptedContent) {
|
||||
event.makeEncrypted("m.room.encrypted", encryptedContent);
|
||||
});
|
||||
|
||||
var participantKeys = [];
|
||||
for (var i = 0; i < users.length; ++i) {
|
||||
var userId = users[i];
|
||||
var devices = this._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: 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
|
||||
*
|
||||
@@ -741,66 +714,45 @@ Crypto.DecryptionError = DecryptionError;
|
||||
*
|
||||
* @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) {
|
||||
var content = event.content;
|
||||
if (content.algorithm !== OLM_ALGORITHM) {
|
||||
throw new DecryptionError("Unknown 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 AlgClass = algorithms.DECRYPTION_CLASSES[content.algorithm];
|
||||
if (!AlgClass) {
|
||||
throw new algorithms.DecryptionError("Unable to decrypt " + content.algorithm);
|
||||
}
|
||||
var alg = new AlgClass({
|
||||
olmDevice: this._olmDevice,
|
||||
});
|
||||
return alg.decryptEvent(event);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle a key event
|
||||
*
|
||||
* @param {module:modules/event~MatrixEvent} event key event
|
||||
*/
|
||||
Crypto.prototype.onRoomKeyEvent = function(event) {
|
||||
var content = event.getContent();
|
||||
var AlgClass = algorithms.DECRYPTION_CLASSES[content.algorithm];
|
||||
if (!AlgClass) {
|
||||
throw new algorithms.DecryptionError(
|
||||
"Unable to handle keys for " + content.algorithm
|
||||
);
|
||||
}
|
||||
var alg = new AlgClass({
|
||||
olmDevice: this._olmDevice,
|
||||
});
|
||||
alg.onRoomKeyEvent(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* @see module:crypto-algorithms/base.DecryptionError
|
||||
*/
|
||||
Crypto.DecryptionError = algorithms.DecryptionError;
|
||||
|
||||
|
||||
/** */
|
||||
module.exports = Crypto;
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ module.exports.MatrixHttpApi = require("./http-api").MatrixHttpApi;
|
||||
module.exports.MatrixError = require("./http-api").MatrixError;
|
||||
/** The {@link module:client.MatrixClient|MatrixClient} class. */
|
||||
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");
|
||||
/** The {@link module:models/event-timeline~EventTimeline} class. */
|
||||
module.exports.EventTimeline = require("./models/event-timeline");
|
||||
|
||||
@@ -29,6 +29,10 @@ limitations under the License.
|
||||
module.exports.EventStatus = {
|
||||
/** The event was not sent and will no longer be retried. */
|
||||
NOT_SENT: "not_sent",
|
||||
|
||||
/** The message is being encrypted */
|
||||
ENCRYPTING: "encrypting",
|
||||
|
||||
/** The event is in the process of being sent. */
|
||||
SENDING: "sending",
|
||||
/** The event is in a queue waiting to be sent. */
|
||||
@@ -228,6 +232,14 @@ module.exports.MatrixEvent.prototype = {
|
||||
return Boolean(this._clearEvent.type);
|
||||
},
|
||||
|
||||
getSenderKey: function() {
|
||||
if (!this.isEncrypted()) {
|
||||
return null;
|
||||
}
|
||||
var c = this.getWireContent();
|
||||
return c.sender_key;
|
||||
},
|
||||
|
||||
getUnsigned: function() {
|
||||
return this.event.unsigned || {};
|
||||
},
|
||||
|
||||
+13
-3
@@ -75,6 +75,7 @@ function synthesizeReceipt(userId, event, receiptType) {
|
||||
* map from event_id to timeline and index.
|
||||
*
|
||||
* @constructor
|
||||
* @alias module:models/room
|
||||
* @param {string} roomId Required. The ID of this room.
|
||||
* @param {Object=} opts Configuration options
|
||||
* @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
|
||||
* in the timeline when the call to <code>sendEvent</code> was made. If
|
||||
* "<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".
|
||||
*
|
||||
* @param {boolean} [opts.timelineSupport = false] Set to true to enable improved
|
||||
@@ -844,8 +845,17 @@ Room.prototype._handleRemoteEcho = function(remoteEvent, localEvent) {
|
||||
*/
|
||||
var ALLOWED_TRANSITIONS = {};
|
||||
|
||||
ALLOWED_TRANSITIONS[EventStatus.SENDING] =
|
||||
[EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.SENT];
|
||||
ALLOWED_TRANSITIONS[EventStatus.ENCRYPTING] = [
|
||||
EventStatus.SENDING,
|
||||
EventStatus.NOT_SENT,
|
||||
];
|
||||
|
||||
ALLOWED_TRANSITIONS[EventStatus.SENDING] = [
|
||||
EventStatus.ENCRYPTING,
|
||||
EventStatus.QUEUED,
|
||||
EventStatus.NOT_SENT,
|
||||
EventStatus.SENT,
|
||||
];
|
||||
|
||||
ALLOWED_TRANSITIONS[EventStatus.QUEUED] =
|
||||
[EventStatus.SENDING, EventStatus.CANCELLED];
|
||||
|
||||
@@ -30,6 +30,7 @@ limitations under the License.
|
||||
* @prop {string} displayName The 'displayname' of the user if known.
|
||||
* @prop {string} avatarUrl The 'avatar_url' of the user if known.
|
||||
* @prop {string} presence The presence enum if known.
|
||||
* @prop {string} presenceStatusMsg The presence status message if known.
|
||||
* @prop {Number} lastActiveAgo The time elapsed in ms since the user interacted
|
||||
* proactively with the server, or we saw a message from the user
|
||||
* @prop {Number} lastPresenceTs Timestamp (ms since the epoch) for when we last
|
||||
@@ -44,6 +45,7 @@ limitations under the License.
|
||||
function User(userId) {
|
||||
this.userId = userId;
|
||||
this.presence = "offline";
|
||||
this.presenceStatusMsg = null;
|
||||
this.displayName = userId;
|
||||
this.avatarUrl = null;
|
||||
this.lastActiveAgo = 0;
|
||||
@@ -96,6 +98,9 @@ User.prototype.setPresenceEvent = function(event) {
|
||||
this.presence = event.getContent().presence;
|
||||
eventsToFire.push("User.lastPresenceTs");
|
||||
|
||||
if (event.getContent().status_msg) {
|
||||
this.presenceStatusMsg = event.getContent().status_msg;
|
||||
}
|
||||
if (event.getContent().displayname) {
|
||||
this.displayName = event.getContent().displayname;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module olmlib
|
||||
*
|
||||
* Utilities common to olm encryption algorithms
|
||||
*/
|
||||
|
||||
var utils = require("./utils");
|
||||
|
||||
/**
|
||||
* matrix algorithm tag for olm
|
||||
*/
|
||||
module.exports.OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
|
||||
|
||||
/**
|
||||
* matrix algorithm tag for megolm
|
||||
*/
|
||||
module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt an event payload for a list of devices
|
||||
*
|
||||
* @param {string} ourDeviceId
|
||||
* @param {module:OlmDevice} olmDevice olm.js wrapper
|
||||
* @param {string[]} participantKeys list of curve25519 keys to encrypt for
|
||||
* @param {object} payloadFields fields to include in the encrypted payload
|
||||
*
|
||||
* @return {object} content for an m.room.encrypted event
|
||||
*/
|
||||
module.exports.encryptMessageForDevices = function(
|
||||
ourDeviceId, olmDevice, participantKeys, payloadFields
|
||||
) {
|
||||
participantKeys.sort();
|
||||
var participantHash = ""; // Olm.sha256(participantKeys.join());
|
||||
var payloadJson = {
|
||||
fingerprint: participantHash,
|
||||
sender_device: ourDeviceId,
|
||||
};
|
||||
utils.extend(payloadJson, payloadFields);
|
||||
|
||||
var ciphertext = {};
|
||||
var payloadString = JSON.stringify(payloadJson);
|
||||
for (var i = 0; i < participantKeys.length; ++i) {
|
||||
var deviceKey = participantKeys[i];
|
||||
var sessionId = olmDevice.getSessionIdForDevice(deviceKey);
|
||||
if (sessionId === null) {
|
||||
// If we don't have a session for a device then
|
||||
// we can't encrypt a message for it.
|
||||
continue;
|
||||
}
|
||||
console.log("Using sessionid " + sessionId + " for device " + deviceKey);
|
||||
ciphertext[deviceKey] = olmDevice.encryptMessage(
|
||||
deviceKey, sessionId, payloadString
|
||||
);
|
||||
}
|
||||
var encryptedContent = {
|
||||
algorithm: module.exports.OLM_ALGORITHM,
|
||||
sender_key: olmDevice.deviceCurve25519Key,
|
||||
ciphertext: ciphertext
|
||||
};
|
||||
return encryptedContent;
|
||||
};
|
||||
+24
-15
@@ -243,25 +243,11 @@ function PushProcessor(client) {
|
||||
return matchingRuleFromKindSet(ev, rulesets.global);
|
||||
};
|
||||
|
||||
var actionListToActionsObject = function(actionlist) {
|
||||
var actionobj = { 'notify': false, 'tweaks': {} };
|
||||
for (var i = 0; i < actionlist.length; ++i) {
|
||||
var action = actionlist[i];
|
||||
if (action === 'notify') {
|
||||
actionobj.notify = true;
|
||||
} else if (typeof action === 'object') {
|
||||
if (action.value === undefined) { action.value = true; }
|
||||
actionobj.tweaks[action.set_tweak] = action.value;
|
||||
}
|
||||
}
|
||||
return actionobj;
|
||||
};
|
||||
|
||||
var pushActionsForEventAndRulesets = function(ev, rulesets) {
|
||||
var rule = matchingRuleForEventWithRulesets(ev, rulesets);
|
||||
if (!rule) { return {}; }
|
||||
|
||||
var actionObj = actionListToActionsObject(rule.actions);
|
||||
var actionObj = PushProcessor.actionListToActionsObject(rule.actions);
|
||||
|
||||
// Some actions are implicit in some situations: we add those here
|
||||
if (actionObj.tweaks.highlight === undefined) {
|
||||
@@ -285,6 +271,28 @@ function PushProcessor(client) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of actions into a object with the actions as keys and their values
|
||||
* eg. [ 'notify', { set_tweak: 'sound', value: 'default' } ]
|
||||
* becomes { notify: true, tweaks: { sound: 'default' } }
|
||||
* @param {array} actionlist The actions list
|
||||
*
|
||||
* @return {object} A object with key 'notify' (true or false) and an object of actions
|
||||
*/
|
||||
PushProcessor.actionListToActionsObject = function(actionlist) {
|
||||
var actionobj = { 'notify': false, 'tweaks': {} };
|
||||
for (var i = 0; i < actionlist.length; ++i) {
|
||||
var action = actionlist[i];
|
||||
if (action === 'notify') {
|
||||
actionobj.notify = true;
|
||||
} else if (typeof action === 'object') {
|
||||
if (action.value === undefined) { action.value = true; }
|
||||
actionobj.tweaks[action.set_tweak] = action.value;
|
||||
}
|
||||
}
|
||||
return actionobj;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} PushAction
|
||||
* @type {Object}
|
||||
@@ -298,3 +306,4 @@ function PushProcessor(client) {
|
||||
|
||||
/** The PushProcessor class. */
|
||||
module.exports = PushProcessor;
|
||||
|
||||
|
||||
@@ -104,6 +104,16 @@ WebStorageSessionStore.prototype = {
|
||||
return getJsonItem(this.store, keyEndToEndSessions(deviceKey));
|
||||
},
|
||||
|
||||
getEndToEndInboundGroupSession: function(senderKey, sessionId) {
|
||||
var key = keyEndToEndInboundGroupSession(senderKey, sessionId);
|
||||
return this.store.getItem(key);
|
||||
},
|
||||
|
||||
storeEndToEndInboundGroupSession: function(senderKey, sessionId, pickledSession) {
|
||||
var key = keyEndToEndInboundGroupSession(senderKey, sessionId);
|
||||
return this.store.setItem(key, pickledSession);
|
||||
},
|
||||
|
||||
/**
|
||||
* Store the end-to-end state for a room.
|
||||
* @param {string} roomId The room's ID.
|
||||
@@ -133,6 +143,10 @@ function keyEndToEndSessions(deviceKey) {
|
||||
return E2E_PREFIX + "sessions/" + deviceKey;
|
||||
}
|
||||
|
||||
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
|
||||
return E2E_PREFIX + "inboundgroupsessions/" + senderKey + "/" + sessionId;
|
||||
}
|
||||
|
||||
function keyEndToEndRoom(roomId) {
|
||||
return E2E_PREFIX + "rooms/" + roomId;
|
||||
}
|
||||
|
||||
@@ -455,6 +455,7 @@ SyncApi.prototype._sync = function(syncOptions) {
|
||||
self._connectionReturnedDefer = null;
|
||||
}
|
||||
this._updateSyncState("STOPPED");
|
||||
return;
|
||||
}
|
||||
|
||||
var filterId = syncOptions.filterId;
|
||||
@@ -612,6 +613,9 @@ SyncApi.prototype._processSyncResponse = function(syncToken, data) {
|
||||
client.store.storeAccountDataEvents(events);
|
||||
events.forEach(
|
||||
function(accountDataEvent) {
|
||||
if (accountDataEvent.getType() == 'm.push_rules') {
|
||||
client.pushRules = accountDataEvent.getContent();
|
||||
}
|
||||
client.emit("accountData", accountDataEvent);
|
||||
return accountDataEvent;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ var DEFAULT_PAGINATE_LOOP_LIMIT = 5;
|
||||
* Construct a TimelineWindow.
|
||||
*
|
||||
* <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
|
||||
* of pagination requests.
|
||||
*
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "0.5.4",
|
||||
"version": "0.5.6",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
var sdk = require("../..");
|
||||
var q = require("q");
|
||||
var HttpBackend = require("../mock-request");
|
||||
var utils = require("../test-utils");
|
||||
var utils = require("../../lib/utils");
|
||||
var test_utils = require("../test-utils");
|
||||
|
||||
function MockStorageApi() {
|
||||
this.data = {};
|
||||
}
|
||||
@@ -119,7 +121,7 @@ function expectBobKeyUpload() {
|
||||
}
|
||||
|
||||
function bobUploadsKeys() {
|
||||
bobClient.uploadKeys(5).catch(utils.failTest);
|
||||
bobClient.uploadKeys(5).catch(test_utils.failTest);
|
||||
return expectBobKeyUpload();
|
||||
}
|
||||
|
||||
@@ -175,6 +177,7 @@ function aliDownloadsKeys() {
|
||||
key: bobDeviceEd25519Key,
|
||||
verified: false,
|
||||
blocked: false,
|
||||
display_name: null,
|
||||
}]);
|
||||
});
|
||||
var p2 = aliQueryKeys();
|
||||
@@ -193,7 +196,7 @@ function aliEnablesEncryption() {
|
||||
// can't query keys before bob has uploaded them
|
||||
expect(bobOneTimeKeys).toBeDefined();
|
||||
|
||||
aliQueryKeys().catch(utils.failTest);
|
||||
aliQueryKeys().catch(test_utils.failTest);
|
||||
aliHttpBackend.when("POST", "/keys/claim").respond(200, function(path, content) {
|
||||
expect(content.one_time_keys[bobUserId][bobDeviceId]).toEqual("curve25519");
|
||||
for (var keyId in bobOneTimeKeys) {
|
||||
@@ -212,8 +215,9 @@ function aliEnablesEncryption() {
|
||||
var p = aliClient.setRoomEncryption(roomId, {
|
||||
algorithm: "m.olm.v1.curve25519-aes-sha2",
|
||||
}).then(function(res) {
|
||||
expect(res.missingUsers).toEqual([]);
|
||||
expect(res.missingDevices).toEqual({});
|
||||
expect(res[aliUserId]).toEqual({});
|
||||
expect(res[bobUserId][bobDeviceId].device).toBeDefined();
|
||||
expect(res[bobUserId][bobDeviceId].sessionId).toBeDefined();
|
||||
expect(aliClient.isRoomEncrypted(roomId)).toBeTruthy();
|
||||
});
|
||||
aliHttpBackend.flush();
|
||||
@@ -221,12 +225,13 @@ function aliEnablesEncryption() {
|
||||
}
|
||||
|
||||
function bobEnablesEncryption() {
|
||||
bobQueryKeys().catch(utils.failTest);
|
||||
bobQueryKeys().catch(test_utils.failTest);
|
||||
return bobClient.setRoomEncryption(roomId, {
|
||||
algorithm: "m.olm.v1.curve25519-aes-sha2",
|
||||
}).then(function(res) {
|
||||
expect(res.missingUsers).toEqual([]);
|
||||
expect(res.missingDevices).toEqual({});
|
||||
expect(res[aliUserId][aliDeviceId].device).toBeDefined();
|
||||
expect(res[aliUserId][aliDeviceId].sessionId).toBeDefined();
|
||||
expect(res[bobUserId]).toEqual({});
|
||||
expect(bobClient.isRoomEncrypted(roomId)).toBeTruthy();
|
||||
});
|
||||
}
|
||||
@@ -234,6 +239,7 @@ function bobEnablesEncryption() {
|
||||
function aliSendsMessage() {
|
||||
return sendMessage(aliHttpBackend, aliClient).then(function(content) {
|
||||
aliMessages.push(content);
|
||||
expect(utils.keys(content.ciphertext)).toEqual([bobDeviceCurve25519Key]);
|
||||
var ciphertext = content.ciphertext[bobDeviceCurve25519Key];
|
||||
expect(ciphertext).toBeDefined();
|
||||
});
|
||||
@@ -244,6 +250,7 @@ function bobSendsMessage() {
|
||||
bobMessages.push(content);
|
||||
var aliKeyId = "curve25519:" + aliDeviceId;
|
||||
var aliDeviceCurve25519Key = aliDeviceKeys.keys[aliKeyId];
|
||||
expect(utils.keys(content.ciphertext)).toEqual([aliDeviceCurve25519Key]);
|
||||
var ciphertext = content.ciphertext[aliDeviceCurve25519Key];
|
||||
expect(ciphertext).toBeDefined();
|
||||
return ciphertext;
|
||||
@@ -290,7 +297,7 @@ function recvMessage(httpBackend, client, message) {
|
||||
syncData.rooms.join[roomId] = {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkEvent({
|
||||
test_utils.mkEvent({
|
||||
type: "m.room.encrypted",
|
||||
room: roomId,
|
||||
content: message
|
||||
@@ -328,7 +335,7 @@ function recvMessage(httpBackend, client, message) {
|
||||
|
||||
|
||||
function aliStartClient() {
|
||||
expectAliKeyUpload().catch(utils.failTest);
|
||||
expectAliKeyUpload().catch(test_utils.failTest);
|
||||
startClient(aliHttpBackend, aliClient);
|
||||
return aliHttpBackend.flush().then(function() {
|
||||
console.log("Ali client started");
|
||||
@@ -336,7 +343,7 @@ function aliStartClient() {
|
||||
}
|
||||
|
||||
function bobStartClient() {
|
||||
expectBobKeyUpload().catch(utils.failTest);
|
||||
expectBobKeyUpload().catch(test_utils.failTest);
|
||||
startClient(bobHttpBackend, bobClient);
|
||||
return bobHttpBackend.flush().then(function() {
|
||||
console.log("Bob client started");
|
||||
@@ -365,11 +372,11 @@ function startClient(httpBackend, client) {
|
||||
syncData.rooms.join[roomId] = {
|
||||
state: {
|
||||
events: [
|
||||
utils.mkMembership({
|
||||
test_utils.mkMembership({
|
||||
mship: "join",
|
||||
user: aliUserId,
|
||||
}),
|
||||
utils.mkMembership({
|
||||
test_utils.mkMembership({
|
||||
mship: "join",
|
||||
user: bobUserId,
|
||||
}),
|
||||
@@ -394,7 +401,7 @@ describe("MatrixClient crypto", function() {
|
||||
aliLocalStore = new MockStorageApi();
|
||||
aliStorage = new sdk.WebStorageSessionStore(aliLocalStore);
|
||||
bobStorage = new sdk.WebStorageSessionStore(new MockStorageApi());
|
||||
utils.beforeEach(this);
|
||||
test_utils.beforeEach(this);
|
||||
|
||||
aliHttpBackend = new HttpBackend();
|
||||
aliClient = sdk.createClient({
|
||||
@@ -433,14 +440,14 @@ describe("MatrixClient crypto", function() {
|
||||
it("Bob uploads without one-time keys and with one-time keys", function(done) {
|
||||
q()
|
||||
.then(bobUploadsKeys)
|
||||
.catch(utils.failTest).done(done);
|
||||
.catch(test_utils.failTest).done(done);
|
||||
});
|
||||
|
||||
it("Ali downloads Bobs keys", function(done) {
|
||||
q()
|
||||
.then(bobUploadsKeys)
|
||||
.then(aliDownloadsKeys)
|
||||
.catch(utils.failTest).done(done);
|
||||
.catch(test_utils.failTest).done(done);
|
||||
});
|
||||
|
||||
it("Ali gets keys with an invalid signature", function(done) {
|
||||
@@ -458,7 +465,7 @@ describe("MatrixClient crypto", function() {
|
||||
// should get an empty list
|
||||
expect(aliClient.listDeviceKeys(bobUserId)).toEqual([]);
|
||||
})
|
||||
.catch(utils.failTest).done(done);
|
||||
.catch(test_utils.failTest).done(done);
|
||||
});
|
||||
|
||||
it("Ali enables encryption", function(done) {
|
||||
@@ -466,7 +473,7 @@ describe("MatrixClient crypto", function() {
|
||||
.then(bobUploadsKeys)
|
||||
.then(aliStartClient)
|
||||
.then(aliEnablesEncryption)
|
||||
.catch(utils.failTest).done(done);
|
||||
.catch(test_utils.failTest).done(done);
|
||||
});
|
||||
|
||||
it("Ali sends a message", function(done) {
|
||||
@@ -475,7 +482,7 @@ describe("MatrixClient crypto", function() {
|
||||
.then(aliStartClient)
|
||||
.then(aliEnablesEncryption)
|
||||
.then(aliSendsMessage)
|
||||
.catch(utils.failTest).done(done);
|
||||
.catch(test_utils.failTest).done(done);
|
||||
});
|
||||
|
||||
it("Bob receives a message", function(done) {
|
||||
@@ -486,7 +493,21 @@ describe("MatrixClient crypto", function() {
|
||||
.then(aliSendsMessage)
|
||||
.then(bobStartClient)
|
||||
.then(bobRecvMessage)
|
||||
.catch(utils.failTest).done(done);
|
||||
.catch(test_utils.failTest).done(done);
|
||||
});
|
||||
|
||||
it("Ali blocks Bob's device", function(done) {
|
||||
q()
|
||||
.then(bobUploadsKeys)
|
||||
.then(aliStartClient)
|
||||
.then(aliEnablesEncryption)
|
||||
.then(function() {
|
||||
aliClient.setDeviceBlocked(bobUserId, bobDeviceId, true);
|
||||
return sendMessage(aliHttpBackend, aliClient);
|
||||
}).then(function(sentContent) {
|
||||
// no unblocked devices, so the ciphertext should be empty
|
||||
expect(sentContent.ciphertext).toEqual({});
|
||||
}).catch(test_utils.failTest).done(done);
|
||||
});
|
||||
|
||||
it("Bob receives two pre-key messages", function(done) {
|
||||
@@ -499,7 +520,7 @@ describe("MatrixClient crypto", function() {
|
||||
.then(bobRecvMessage)
|
||||
.then(aliSendsMessage)
|
||||
.then(bobRecvMessage)
|
||||
.catch(utils.failTest).done(done);
|
||||
.catch(test_utils.failTest).done(done);
|
||||
});
|
||||
|
||||
it("Bob replies to the message", function(done) {
|
||||
@@ -514,6 +535,6 @@ describe("MatrixClient crypto", function() {
|
||||
.then(bobSendsMessage).then(function(ciphertext) {
|
||||
expect(ciphertext.type).toEqual(1);
|
||||
}).then(aliRecvMessage)
|
||||
.catch(utils.failTest).done(done);
|
||||
.catch(test_utils.failTest).done(done);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -236,27 +236,22 @@ describe("MatrixClient", function() {
|
||||
});
|
||||
|
||||
client.downloadKeys(["boris", "chaz", "dave"]).then(function(res) {
|
||||
expect(res).toEqual({
|
||||
boris: {
|
||||
dev1: {
|
||||
verified: 0, // DeviceVerification.UNVERIFIED
|
||||
keys: { "ed25519:dev1": ed25519key },
|
||||
algorithms: ["1"],
|
||||
unsigned: { "abc": "def" },
|
||||
},
|
||||
},
|
||||
chaz: {
|
||||
dev2: {
|
||||
verified: 0, // DeviceVerification.UNVERIFIED
|
||||
keys: { "ed25519:dev2" : ed25519key },
|
||||
algorithms: ["2"],
|
||||
unsigned: { "ghi": "def" },
|
||||
},
|
||||
},
|
||||
dave: {
|
||||
// dave's key fails validation.
|
||||
},
|
||||
assertObjectContains(res.boris.dev1, {
|
||||
verified: 0, // DeviceVerification.UNVERIFIED
|
||||
keys: { "ed25519:dev1": ed25519key },
|
||||
algorithms: ["1"],
|
||||
unsigned: { "abc": "def" },
|
||||
});
|
||||
|
||||
assertObjectContains(res.chaz.dev2, {
|
||||
verified: 0, // DeviceVerification.UNVERIFIED
|
||||
keys: { "ed25519:dev2" : ed25519key },
|
||||
algorithms: ["2"],
|
||||
unsigned: { "ghi": "def" },
|
||||
});
|
||||
|
||||
// dave's key fails validation.
|
||||
expect(res.dave).toEqual({});
|
||||
}).catch(utils.failTest).done(done);
|
||||
|
||||
httpBackend.flush();
|
||||
@@ -278,3 +273,11 @@ describe("MatrixClient", function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function assertObjectContains(obj, expected) {
|
||||
for (var k in expected) {
|
||||
if (expected.hasOwnProperty(k)) {
|
||||
expect(obj[k]).toEqual(expected[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
var q = require("q");
|
||||
var sdk = require("../..");
|
||||
var HttpBackend = require("../mock-request");
|
||||
var utils = require("../test-utils");
|
||||
@@ -69,8 +68,8 @@ describe("MatrixClient retrying", function() {
|
||||
expect(ev1.status).toEqual(EventStatus.SENDING);
|
||||
expect(ev2.status).toEqual(EventStatus.SENDING);
|
||||
|
||||
// give the reactor a chance to run, so that ev2 gets queued
|
||||
q().then(function() {
|
||||
// the first message should get sent, and the second should get queued
|
||||
httpBackend.when("PUT", "/send/m.room.message/").check(function(rq) {
|
||||
// ev2 should now have been queued
|
||||
expect(ev2.status).toEqual(EventStatus.QUEUED);
|
||||
|
||||
@@ -82,12 +81,9 @@ describe("MatrixClient retrying", function() {
|
||||
// shouldn't be able to cancel the first message yet
|
||||
expect(function() { client.cancelPendingEvent(ev1); })
|
||||
.toThrow();
|
||||
}).respond(400); // fail the first message
|
||||
|
||||
// fail the first send
|
||||
httpBackend.when("PUT", "/send/m.room.message/")
|
||||
.respond(400);
|
||||
return httpBackend.flush();
|
||||
}).then(function() {
|
||||
httpBackend.flush().then(function() {
|
||||
expect(ev1.status).toEqual(EventStatus.NOT_SENT);
|
||||
expect(tl.length).toEqual(1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user