You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-28 05:03:59 +03:00
Factor out OlmDevice from client.js
MatrixClient contains quite a lot of boilerplate for manipulating the Olm things, which quite nicely factors out to a separate object.
This commit is contained in:
329
lib/OlmDevice.js
Normal file
329
lib/OlmDevice.js
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
/*
|
||||||
|
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";
|
||||||
|
|
||||||
|
var Olm = require("olm");
|
||||||
|
var utils = require("./utils");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the olm cryptography functions. Each OlmDevice has a single
|
||||||
|
* OlmAccount and a number of OlmSessions.
|
||||||
|
*
|
||||||
|
* Accounts and sessions are kept pickled in a sessionStore.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Object} sessionStore A store to be used for data in end-to-end
|
||||||
|
* crypto
|
||||||
|
*
|
||||||
|
* @property {string} deviceCurve25519Key Curve25519 key for the account
|
||||||
|
* @property {string} deviceEd25519Key Ed25519 key for the account
|
||||||
|
*/
|
||||||
|
function OlmDevice(sessionStore) {
|
||||||
|
this._sessionStore = sessionStore;
|
||||||
|
this._pickleKey = "DEFAULT_KEY";
|
||||||
|
|
||||||
|
var e2eKeys;
|
||||||
|
var account = new Olm.Account();
|
||||||
|
try {
|
||||||
|
var e2eAccount = this._sessionStore.getEndToEndAccount();
|
||||||
|
if (e2eAccount === null) {
|
||||||
|
account.create();
|
||||||
|
var pickled = account.pickle(this._pickleKey);
|
||||||
|
this._sessionStore.storeEndToEndAccount(pickled);
|
||||||
|
} else {
|
||||||
|
account.unpickle(this._pickleKey, e2eAccount);
|
||||||
|
}
|
||||||
|
e2eKeys = JSON.parse(account.identity_keys());
|
||||||
|
} finally {
|
||||||
|
account.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deviceCurve25519Key = e2eKeys.curve25519;
|
||||||
|
this.deviceEd25519Key = e2eKeys.ed25519;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs a message with the ed25519 key for this account.
|
||||||
|
*
|
||||||
|
* @param {string} message message to be signed
|
||||||
|
* @return {string} base64-encoded signature
|
||||||
|
*/
|
||||||
|
OlmDevice.prototype.sign = function(message) {
|
||||||
|
var account = new Olm.Account();
|
||||||
|
try {
|
||||||
|
var pickledAccount = this._sessionStore.getEndToEndAccount();
|
||||||
|
account.unpickle(this._pickleKey, pickledAccount);
|
||||||
|
return account.sign(message);
|
||||||
|
} finally {
|
||||||
|
account.free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current (unused, unpublished) one-time keys for this account.
|
||||||
|
*
|
||||||
|
* @return {object} one time keys; an object with the single property
|
||||||
|
* <tt>curve25519<tt>, which is itself an object mapping key id to Curve25519
|
||||||
|
* key.
|
||||||
|
*/
|
||||||
|
OlmDevice.prototype.getOneTimeKeys = function() {
|
||||||
|
var account = new Olm.Account();
|
||||||
|
try {
|
||||||
|
var pickledAccount = this._sessionStore.getEndToEndAccount();
|
||||||
|
account.unpickle(this._pickleKey, pickledAccount);
|
||||||
|
return JSON.parse(account.one_time_keys());
|
||||||
|
} finally {
|
||||||
|
account.free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum number of one-time keys we can store.
|
||||||
|
*
|
||||||
|
* @return {number} number of keys
|
||||||
|
*/
|
||||||
|
OlmDevice.prototype.maxNumberOfOneTimeKeys = function() {
|
||||||
|
var account = new Olm.Account();
|
||||||
|
try {
|
||||||
|
var pickledAccount = this._sessionStore.getEndToEndAccount();
|
||||||
|
account.unpickle(this._pickleKey, pickledAccount);
|
||||||
|
return account.max_number_of_one_time_keys();
|
||||||
|
} finally {
|
||||||
|
account.free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks all of the one-time keys as published.
|
||||||
|
*/
|
||||||
|
OlmDevice.prototype.markKeysAsPublished = function() {
|
||||||
|
var account = new Olm.Account();
|
||||||
|
try {
|
||||||
|
var pickledAccount = this._sessionStore.getEndToEndAccount();
|
||||||
|
account.unpickle(this._pickleKey, pickledAccount);
|
||||||
|
account.mark_keys_as_published();
|
||||||
|
pickledAccount = account.pickle(this._pickleKey);
|
||||||
|
this._sessionStore.storeEndToEndAccount(pickledAccount);
|
||||||
|
} finally {
|
||||||
|
account.free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate some new one-time keys
|
||||||
|
*
|
||||||
|
* @param {number} numKeys number of keys to generate
|
||||||
|
*/
|
||||||
|
OlmDevice.prototype.generateOneTimeKeys = function(numKeys) {
|
||||||
|
var account = new Olm.Account();
|
||||||
|
try {
|
||||||
|
var pickledAccount = this._sessionStore.getEndToEndAccount();
|
||||||
|
account.unpickle(this._pickleKey, pickledAccount);
|
||||||
|
account.generate_one_time_keys(numKeys);
|
||||||
|
pickledAccount = account.pickle(this._pickleKey);
|
||||||
|
this._sessionStore.storeEndToEndAccount(pickledAccount);
|
||||||
|
} finally {
|
||||||
|
account.free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a new outbound session
|
||||||
|
*
|
||||||
|
* The new session will be stored in the sessionStore.
|
||||||
|
*
|
||||||
|
* @param {string} theirIdentityKey remote user's Curve25519 identity key
|
||||||
|
* @param {string} theirOneTimeKey remote user's one-time Curve25519 key
|
||||||
|
* @return {string} sessionId for the outbound session.
|
||||||
|
*/
|
||||||
|
OlmDevice.prototype.createOutboundSession = function(
|
||||||
|
theirIdentityKey, theirOneTimeKey
|
||||||
|
) {
|
||||||
|
var account = new Olm.Account();
|
||||||
|
var session = new Olm.Session();
|
||||||
|
try {
|
||||||
|
var pickledAccount = this._sessionStore.getEndToEndAccount();
|
||||||
|
account.unpickle(this._pickleKey, pickledAccount);
|
||||||
|
|
||||||
|
session.create_outbound(account, theirIdentityKey, theirOneTimeKey);
|
||||||
|
|
||||||
|
var pickledSession = session.pickle(this._pickleKey);
|
||||||
|
var sessionId = session.session_id();
|
||||||
|
this._sessionStore.storeEndToEndSession(
|
||||||
|
theirIdentityKey, sessionId, pickledSession
|
||||||
|
);
|
||||||
|
return sessionId;
|
||||||
|
} finally {
|
||||||
|
session.free();
|
||||||
|
account.free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a new inbound session, given an incoming message
|
||||||
|
*
|
||||||
|
* @param {string} theirDeviceIdentityKey remote user's Curve25519 identity key
|
||||||
|
* @param {number} message_type message_type field from the received message (must be 0)
|
||||||
|
* @param {string} ciphertext base64-encoded body from the received message
|
||||||
|
*
|
||||||
|
* @return {string} decrypted payload
|
||||||
|
*
|
||||||
|
* @raises {Error} if the received message was not valid (for instance, it
|
||||||
|
* didn't use a valid one-time key).
|
||||||
|
*/
|
||||||
|
OlmDevice.prototype.createInboundSession = function(
|
||||||
|
theirDeviceIdentityKey, message_type, ciphertext
|
||||||
|
) {
|
||||||
|
if (message_type !== 0) {
|
||||||
|
throw new Error("Need message_type == 0 to create inbound session");
|
||||||
|
}
|
||||||
|
|
||||||
|
var account = new Olm.Account();
|
||||||
|
var session = new Olm.Session();
|
||||||
|
try {
|
||||||
|
var pickledAccount = this._sessionStore.getEndToEndAccount();
|
||||||
|
account.unpickle(this._pickleKey, pickledAccount);
|
||||||
|
|
||||||
|
session.create_inbound_from(account, theirDeviceIdentityKey, ciphertext);
|
||||||
|
account.remove_one_time_keys(session);
|
||||||
|
|
||||||
|
pickledAccount = account.pickle(this._pickleKey);
|
||||||
|
this._sessionStore.storeEndToEndAccount(pickledAccount);
|
||||||
|
|
||||||
|
var payloadString = session.decrypt(message_type, ciphertext);
|
||||||
|
|
||||||
|
var sessionId = session.session_id();
|
||||||
|
var pickledSession = session.pickle(this._pickleKey);
|
||||||
|
this._sessionStore.storeEndToEndSession(
|
||||||
|
theirDeviceIdentityKey, sessionId, pickledSession
|
||||||
|
);
|
||||||
|
|
||||||
|
return payloadString;
|
||||||
|
} finally {
|
||||||
|
session.free();
|
||||||
|
account.free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of known session IDs for the given device
|
||||||
|
*
|
||||||
|
* @param {string} theirDeviceIdentityKey Curve25519 identity key for the
|
||||||
|
* remote device
|
||||||
|
* @return {string[]} a list of known session ids for the device
|
||||||
|
*/
|
||||||
|
OlmDevice.prototype.getSessionIdsForDevice = function(theirDeviceIdentityKey) {
|
||||||
|
var sessions = this._sessionStore.getEndToEndSessions(
|
||||||
|
theirDeviceIdentityKey
|
||||||
|
);
|
||||||
|
return utils.keys(sessions);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt an outgoing message using an existing session
|
||||||
|
*
|
||||||
|
* @param {string} theirDeviceIdentityKey Curve25519 identity key for the
|
||||||
|
* remote device
|
||||||
|
* @param {string} sessionId the id of the active session
|
||||||
|
* @param {string} payloadString payload to be encrypted and sent
|
||||||
|
*
|
||||||
|
* @return {string} ciphertext
|
||||||
|
*/
|
||||||
|
OlmDevice.prototype.encryptMessage = function(
|
||||||
|
theirDeviceIdentityKey, sessionId, payloadString
|
||||||
|
) {
|
||||||
|
var sessions = this._sessionStore.getEndToEndSessions(
|
||||||
|
theirDeviceIdentityKey
|
||||||
|
);
|
||||||
|
var pickledSession = sessions[sessionId];
|
||||||
|
|
||||||
|
var session = new Olm.Session();
|
||||||
|
|
||||||
|
try {
|
||||||
|
session.unpickle(this._pickleKey, pickledSession);
|
||||||
|
var res = session.encrypt(payloadString);
|
||||||
|
pickledSession = session.pickle(this._pickleKey);
|
||||||
|
this._sessionStore.storeEndToEndSession(
|
||||||
|
theirDeviceIdentityKey, sessionId, pickledSession
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
} finally {
|
||||||
|
session.free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt an incoming message using an existing session
|
||||||
|
*
|
||||||
|
* @param {string} theirDeviceIdentityKey Curve25519 identity key for the
|
||||||
|
* remote device
|
||||||
|
* @param {string} sessionId the id of the active session
|
||||||
|
* @param {number} message_type message_type field from the received message
|
||||||
|
* @param {string} ciphertext base64-encoded body from the received message
|
||||||
|
*
|
||||||
|
* @return {string} decrypted payload
|
||||||
|
*
|
||||||
|
* @raises {Error} if the received message was not valid (for instance, it
|
||||||
|
* did not match this session).
|
||||||
|
*/
|
||||||
|
OlmDevice.prototype.decryptMessage = function(
|
||||||
|
theirDeviceIdentityKey, sessionId, message_type, ciphertext
|
||||||
|
) {
|
||||||
|
var sessions = this._sessionStore.getEndToEndSessions(
|
||||||
|
theirDeviceIdentityKey
|
||||||
|
);
|
||||||
|
var pickledSession = sessions[sessionId];
|
||||||
|
|
||||||
|
var session = new Olm.Session();
|
||||||
|
try {
|
||||||
|
session.unpickle(this._pickleKey, pickledSession);
|
||||||
|
var matchesInbound = message_type === 0 && session.matches_inbound(ciphertext);
|
||||||
|
var payloadString = null;
|
||||||
|
try {
|
||||||
|
payloadString = session.decrypt(message_type, ciphertext);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
"Failed to decrypt with an existing session: " + e.message
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
matchesInbound: matchesInbound,
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// successfully decrypted: update the session
|
||||||
|
pickledSession = session.pickle(this._pickleKey);
|
||||||
|
this._sessionStore.storeEndToEndSession(
|
||||||
|
theirDeviceIdentityKey, sessionId, pickledSession
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
matchesInbound: matchesInbound,
|
||||||
|
payload: payloadString,
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
session.free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** */
|
||||||
|
module.exports = OlmDevice;
|
||||||
207
lib/client.js
207
lib/client.js
@@ -41,10 +41,8 @@ var SCROLLBACK_DELAY_MS = 3000;
|
|||||||
var CRYPTO_ENABLED = false;
|
var CRYPTO_ENABLED = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var Olm = require("olm");
|
var OlmDevice = require("./OlmDevice");
|
||||||
if (Olm.Account && Olm.Session) {
|
|
||||||
CRYPTO_ENABLED = true;
|
CRYPTO_ENABLED = true;
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Olm not installed.
|
// Olm not installed.
|
||||||
}
|
}
|
||||||
@@ -69,10 +67,18 @@ var OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
|
|||||||
* {@link requestFunction} for more information.
|
* {@link requestFunction} for more information.
|
||||||
*
|
*
|
||||||
* @param {string} opts.accessToken The access_token for this user.
|
* @param {string} opts.accessToken The access_token for this user.
|
||||||
|
*
|
||||||
* @param {string} opts.userId The user ID for this user.
|
* @param {string} opts.userId The user ID for this user.
|
||||||
* @param {Object} opts.store Optional. The data store to use. If not specified,
|
*
|
||||||
|
* @param {Object=} opts.store The data store to use. If not specified,
|
||||||
* this client will not store any HTTP responses.
|
* this client will not store any HTTP responses.
|
||||||
*
|
*
|
||||||
|
* @param {string=} opts.deviceId A unique identifier for this device, for use
|
||||||
|
* in end-to-end crypto. If not specified, end-to-end crypto will be disabled.
|
||||||
|
*
|
||||||
|
* @param {Object=} opts.sessionStore A store to be used for end-to-end crypto
|
||||||
|
* session data. If not specified, end-to-end crypto will be disabled.
|
||||||
|
*
|
||||||
* @param {Object} opts.scheduler Optional. The scheduler to use. If not
|
* @param {Object} opts.scheduler Optional. The scheduler to use. If not
|
||||||
* specified, this client will not retry requests on failure. This client
|
* specified, this client will not retry requests on failure. This client
|
||||||
* will supply its own processing function to
|
* will supply its own processing function to
|
||||||
@@ -97,37 +103,30 @@ function MatrixClient(opts) {
|
|||||||
|
|
||||||
this.store = opts.store || new StubStore();
|
this.store = opts.store || new StubStore();
|
||||||
this.sessionStore = opts.sessionStore || null;
|
this.sessionStore = opts.sessionStore || null;
|
||||||
this.accountKey = "DEFAULT_KEY";
|
|
||||||
this.deviceId = opts.deviceId;
|
this.deviceId = opts.deviceId;
|
||||||
|
this._olmDevice = null;
|
||||||
|
|
||||||
if (CRYPTO_ENABLED && this.sessionStore !== null) {
|
if (CRYPTO_ENABLED && this.sessionStore !== null) {
|
||||||
var e2eAccount = this.sessionStore.getEndToEndAccount();
|
this._olmDevice = new OlmDevice(opts.sessionStore);
|
||||||
var account = new Olm.Account();
|
|
||||||
try {
|
|
||||||
if (e2eAccount === null) {
|
|
||||||
account.create();
|
|
||||||
} else {
|
|
||||||
account.unpickle(this.accountKey, e2eAccount);
|
|
||||||
}
|
|
||||||
var e2eKeys = JSON.parse(account.identity_keys());
|
|
||||||
var json = '{"algorithms":["' + OLM_ALGORITHM + '"]';
|
var json = '{"algorithms":["' + OLM_ALGORITHM + '"]';
|
||||||
json += ',"device_id":"' + this.deviceId + '"';
|
json += ',"device_id":"' + this.deviceId + '"';
|
||||||
json += ',"keys":';
|
json += ',"keys":';
|
||||||
json += '{"ed25519:' + this.deviceId + '":';
|
json += '{"ed25519:' + this.deviceId + '":';
|
||||||
json += JSON.stringify(e2eKeys.ed25519);
|
json += JSON.stringify(this._olmDevice.deviceEd25519Key);
|
||||||
json += ',"curve25519:' + this.deviceId + '":';
|
json += ',"curve25519:' + this.deviceId + '":';
|
||||||
json += JSON.stringify(e2eKeys.curve25519);
|
json += JSON.stringify(this._olmDevice.deviceCurve25519Key);
|
||||||
json += '}';
|
json += '}';
|
||||||
json += ',"user_id":' + JSON.stringify(opts.userId);
|
json += ',"user_id":' + JSON.stringify(opts.userId);
|
||||||
json += '}';
|
json += '}';
|
||||||
var signature = account.sign(json);
|
var signature = this._olmDevice.sign(json);
|
||||||
this.deviceKeys = JSON.parse(json);
|
this.deviceKeys = JSON.parse(json);
|
||||||
var signatures = {};
|
var signatures = {};
|
||||||
signatures[opts.userId] = {};
|
signatures[opts.userId] = {};
|
||||||
signatures[opts.userId]["ed25519:" + this.deviceId] = signature;
|
signatures[opts.userId]["ed25519:" + this.deviceId] = signature;
|
||||||
this.deviceKeys.signatures = signatures;
|
this.deviceKeys.signatures = signatures;
|
||||||
this.deviceCurve25519Key = e2eKeys.curve25519;
|
|
||||||
var pickled = account.pickle(this.accountKey);
|
|
||||||
this.sessionStore.storeEndToEndAccount(pickled);
|
|
||||||
var myDevices = this.sessionStore.getEndToEndDevicesForUser(
|
var myDevices = this.sessionStore.getEndToEndDevicesForUser(
|
||||||
opts.userId
|
opts.userId
|
||||||
) || {};
|
) || {};
|
||||||
@@ -135,9 +134,6 @@ function MatrixClient(opts) {
|
|||||||
this.sessionStore.storeEndToEndDevicesForUser(
|
this.sessionStore.storeEndToEndDevicesForUser(
|
||||||
opts.userId, myDevices
|
opts.userId, myDevices
|
||||||
);
|
);
|
||||||
} finally {
|
|
||||||
account.free();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.scheduler = opts.scheduler;
|
this.scheduler = opts.scheduler;
|
||||||
if (this.scheduler) {
|
if (this.scheduler) {
|
||||||
@@ -311,54 +307,29 @@ MatrixClient.prototype.uploadKeys = function(maxKeys, deferred) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
return _doKeyUpload(this).then(function(res) {
|
return _doKeyUpload(this).then(function(res) {
|
||||||
var keyCount = res.one_time_key_counts.curve25519 || 0;
|
var keyCount = res.one_time_key_counts.curve25519 || 0;
|
||||||
|
var maxOneTimeKeys = self._olmDevice.maxNumberOfOneTimeKeys();
|
||||||
var pickled = self.sessionStore.getEndToEndAccount();
|
|
||||||
|
|
||||||
var numberToGenerate;
|
|
||||||
var account = new Olm.Account();
|
|
||||||
try {
|
|
||||||
account.unpickle(self.accountKey, pickled);
|
|
||||||
|
|
||||||
var maxOneTimeKeys = account.max_number_of_one_time_keys();
|
|
||||||
var keyLimit = Math.floor(maxOneTimeKeys / 2);
|
var keyLimit = Math.floor(maxOneTimeKeys / 2);
|
||||||
numberToGenerate = Math.max(keyLimit - keyCount, 0);
|
var numberToGenerate = Math.max(keyLimit - keyCount, 0);
|
||||||
if (maxKeys !== undefined) {
|
if (maxKeys !== undefined) {
|
||||||
numberToGenerate = Math.min(numberToGenerate, maxKeys);
|
numberToGenerate = Math.min(numberToGenerate, maxKeys);
|
||||||
}
|
}
|
||||||
if (numberToGenerate > 0) {
|
|
||||||
account.generate_one_time_keys(numberToGenerate);
|
if (numberToGenerate <= 0) {
|
||||||
}
|
|
||||||
pickled = account.pickle(self.accountKey);
|
|
||||||
self.sessionStore.storeEndToEndAccount(pickled);
|
|
||||||
} finally {
|
|
||||||
account.free();
|
|
||||||
}
|
|
||||||
if (numberToGenerate > 0) {
|
|
||||||
return _doKeyUpload(self);
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self._olmDevice.generateOneTimeKeys(numberToGenerate);
|
||||||
|
return _doKeyUpload(self);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// build the upload request, and return a promise which resolves to the response
|
// build the upload request, and return a promise which resolves to the response
|
||||||
function _doKeyUpload(client) {
|
function _doKeyUpload(client) {
|
||||||
if (!CRYPTO_ENABLED || client.sessionStore === null) {
|
if (!client._olmDevice) {
|
||||||
return q.reject(new Error("End-to-end encryption disabled"));
|
return q.reject(new Error("End-to-end encryption disabled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
var pickled = client.sessionStore.getEndToEndAccount();
|
var oneTimeKeys = client._olmDevice.getOneTimeKeys();
|
||||||
if (!pickled) {
|
|
||||||
return q.reject(new Error("End-to-end account not found"));
|
|
||||||
}
|
|
||||||
var account = new Olm.Account();
|
|
||||||
var oneTimeKeys;
|
|
||||||
try {
|
|
||||||
account.unpickle(client.accountKey, pickled);
|
|
||||||
oneTimeKeys = JSON.parse(account.one_time_keys());
|
|
||||||
} finally {
|
|
||||||
account.free();
|
|
||||||
}
|
|
||||||
var oneTimeJson = {};
|
var oneTimeJson = {};
|
||||||
|
|
||||||
for (var keyId in oneTimeKeys.curve25519) {
|
for (var keyId in oneTimeKeys.curve25519) {
|
||||||
@@ -374,15 +345,7 @@ function _doKeyUpload(client) {
|
|||||||
return client._http.authedRequestWithPrefix(
|
return client._http.authedRequestWithPrefix(
|
||||||
undefined, "POST", path, undefined, content, httpApi.PREFIX_UNSTABLE
|
undefined, "POST", path, undefined, content, httpApi.PREFIX_UNSTABLE
|
||||||
).then(function(res) {
|
).then(function(res) {
|
||||||
var account = new Olm.Account();
|
client._olmDevice.markKeysAsPublished();
|
||||||
try {
|
|
||||||
account.unpickle(client.accountKey, pickled);
|
|
||||||
account.mark_keys_as_published();
|
|
||||||
pickled = account.pickle(client.accountKey);
|
|
||||||
client.sessionStore.storeEndToEndAccount(pickled);
|
|
||||||
} finally {
|
|
||||||
account.free();
|
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -484,9 +447,10 @@ MatrixClient.prototype.listDeviceKeys = function(userId) {
|
|||||||
* @return {Object} A promise that will resolve when encryption is setup.
|
* @return {Object} A promise that will resolve when encryption is setup.
|
||||||
*/
|
*/
|
||||||
MatrixClient.prototype.setRoomEncryption = function(roomId, config) {
|
MatrixClient.prototype.setRoomEncryption = function(roomId, config) {
|
||||||
if (!this.sessionStore || !CRYPTO_ENABLED) {
|
if (!this._olmDevice) {
|
||||||
return q.reject(new Error("End-to-End encryption disabled"));
|
return q.reject(new Error("End-to-End encryption disabled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.algorithm === OLM_ALGORITHM) {
|
if (config.algorithm === OLM_ALGORITHM) {
|
||||||
if (!config.members) {
|
if (!config.members) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -505,7 +469,7 @@ MatrixClient.prototype.setRoomEncryption = function(roomId, config) {
|
|||||||
if (devices.hasOwnProperty(deviceId)) {
|
if (devices.hasOwnProperty(deviceId)) {
|
||||||
var keys = devices[deviceId];
|
var keys = devices[deviceId];
|
||||||
var key = keys.keys["curve25519:" + deviceId];
|
var key = keys.keys["curve25519:" + deviceId];
|
||||||
if (key == this.deviceCurve25519Key) {
|
if (key == this._olmDevice.deviceCurve25519Key) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!this.sessionStore.getEndToEndSessions(key)) {
|
if (!this.sessionStore.getEndToEndSessions(key)) {
|
||||||
@@ -543,21 +507,9 @@ MatrixClient.prototype.setRoomEncryption = function(roomId, config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oneTimeKey) {
|
if (oneTimeKey) {
|
||||||
var session = new Olm.Session();
|
self._olmDevice.createOutboundSession(
|
||||||
var account = new Olm.Account();
|
device[2], oneTimeKey
|
||||||
try {
|
|
||||||
var pickled = self.sessionStore.getEndToEndAccount();
|
|
||||||
account.unpickle(self.accountKey, pickled);
|
|
||||||
session.create_outbound(account, device[2], oneTimeKey);
|
|
||||||
var sessionId = session.session_id();
|
|
||||||
pickled = session.pickle(self.accountKey);
|
|
||||||
self.sessionStore.storeEndToEndSession(
|
|
||||||
device[2], sessionId, pickled
|
|
||||||
);
|
);
|
||||||
} finally {
|
|
||||||
session.free();
|
|
||||||
account.free();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
missing[device[0]] = missing[device[0]] || [];
|
missing[device[0]] = missing[device[0]] || [];
|
||||||
missing[device[0]].push([device[1]]);
|
missing[device[0]].push([device[1]]);
|
||||||
@@ -1032,18 +984,10 @@ function _encryptMessage(client, roomId, e2eRoomInfo, eventType, content,
|
|||||||
var payloadString = JSON.stringify(payloadJson);
|
var payloadString = JSON.stringify(payloadJson);
|
||||||
for (i = 0; i < participantKeys.length; ++i) {
|
for (i = 0; i < participantKeys.length; ++i) {
|
||||||
var deviceKey = participantKeys[i];
|
var deviceKey = participantKeys[i];
|
||||||
if (deviceKey == client.deviceCurve25519Key) {
|
if (deviceKey == client._olmDevice.deviceCurve25519Key) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var sessions = client.sessionStore.getEndToEndSessions(
|
var sessionIds = client._olmDevice.getSessionIdsForDevice(deviceKey);
|
||||||
deviceKey
|
|
||||||
);
|
|
||||||
var sessionIds = [];
|
|
||||||
for (var sessionId in sessions) {
|
|
||||||
if (sessions.hasOwnProperty(sessionId)) {
|
|
||||||
sessionIds.push(sessionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Use the session with the lowest ID.
|
// Use the session with the lowest ID.
|
||||||
sessionIds.sort();
|
sessionIds.sort();
|
||||||
if (sessionIds.length === 0) {
|
if (sessionIds.length === 0) {
|
||||||
@@ -1051,22 +995,14 @@ function _encryptMessage(client, roomId, e2eRoomInfo, eventType, content,
|
|||||||
// we can't encrypt a message for it.
|
// we can't encrypt a message for it.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sessionId = sessionIds[0];
|
var sessionId = sessionIds[0];
|
||||||
var session = new Olm.Session();
|
ciphertext[deviceKey] = client._olmDevice.encryptMessage(
|
||||||
try {
|
deviceKey, sessionId, payloadString
|
||||||
session.unpickle(client.accountKey, sessions[sessionId]);
|
|
||||||
ciphertext[deviceKey] = session.encrypt(payloadString);
|
|
||||||
var pickled = session.pickle(client.accountKey);
|
|
||||||
client.sessionStore.storeEndToEndSession(
|
|
||||||
deviceKey, sessionId, pickled
|
|
||||||
);
|
);
|
||||||
} finally {
|
|
||||||
session.free();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var encryptedContent = {
|
var encryptedContent = {
|
||||||
algorithm: e2eRoomInfo.algorithm,
|
algorithm: e2eRoomInfo.algorithm,
|
||||||
sender_key: client.deviceCurve25519Key,
|
sender_key: client._olmDevice.deviceCurve25519Key,
|
||||||
ciphertext: ciphertext
|
ciphertext: ciphertext
|
||||||
};
|
};
|
||||||
return encryptedContent;
|
return encryptedContent;
|
||||||
@@ -1090,59 +1026,38 @@ function _decryptMessage(client, event) {
|
|||||||
if (!ciphertext) {
|
if (!ciphertext) {
|
||||||
return _badEncryptedMessage(event, "**Missing ciphertext**");
|
return _badEncryptedMessage(event, "**Missing ciphertext**");
|
||||||
}
|
}
|
||||||
if (!(client.deviceCurve25519Key in content.ciphertext)) {
|
if (!(client._olmDevice.deviceCurve25519Key in content.ciphertext)) {
|
||||||
return _badEncryptedMessage(event, "**Not included in recipients**");
|
return _badEncryptedMessage(event, "**Not included in recipients**");
|
||||||
}
|
}
|
||||||
var message = content.ciphertext[client.deviceCurve25519Key];
|
var message = content.ciphertext[client._olmDevice.deviceCurve25519Key];
|
||||||
var sessions = client.sessionStore.getEndToEndSessions(deviceKey);
|
var sessionIds = client._olmDevice.getSessionIdsForDevice(deviceKey);
|
||||||
var payloadString = null;
|
var payloadString = null;
|
||||||
var foundSession = false;
|
var foundSession = false;
|
||||||
var session;
|
for (var i = 0; i < sessionIds.length; i++) {
|
||||||
for (var sessionId in sessions) {
|
var sessionId = sessionIds[i];
|
||||||
if (sessions.hasOwnProperty(sessionId)) {
|
var res = client._olmDevice.decryptMessage(
|
||||||
session = new Olm.Session();
|
deviceKey, sessionId, message.type, message.body
|
||||||
try {
|
);
|
||||||
session.unpickle(client.accountKey, sessions[sessionId]);
|
payloadString = res.payload;
|
||||||
if (message.type === 0 && session.matches_inbound(message.body)) {
|
if (payloadString) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.matchesInbound) {
|
||||||
|
// this was a prekey message which matched this session; don't
|
||||||
|
// create a new session.
|
||||||
foundSession = true;
|
foundSession = true;
|
||||||
}
|
break;
|
||||||
payloadString = session.decrypt(message.type, message.body);
|
|
||||||
var pickled = session.pickle(client.accountKey);
|
|
||||||
client.sessionStore.storeEndToEndSession(
|
|
||||||
deviceKey, sessionId, pickled
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
// Failed to decrypt with an existing session.
|
|
||||||
console.log(
|
|
||||||
"Failed to decrypt with an existing session: " + e.message
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
session.free();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 0 && !foundSession && payloadString === null) {
|
if (message.type === 0 && !foundSession && payloadString === null) {
|
||||||
var account = new Olm.Account();
|
|
||||||
session = new Olm.Session();
|
|
||||||
try {
|
try {
|
||||||
var account_data = client.sessionStore.getEndToEndAccount();
|
payloadString = client._olmDevice.createInboundSession(
|
||||||
account.unpickle(client.accountKey, account_data);
|
deviceKey, message.type, message.body
|
||||||
session.create_inbound_from(account, deviceKey, message.body);
|
|
||||||
payloadString = session.decrypt(message.type, message.body);
|
|
||||||
account.remove_one_time_keys(session);
|
|
||||||
var pickledSession = session.pickle(client.accountKey);
|
|
||||||
var pickledAccount = account.pickle(client.accountKey);
|
|
||||||
sessionId = session.session_id();
|
|
||||||
client.sessionStore.storeEndToEndSession(
|
|
||||||
deviceKey, sessionId, pickledSession
|
|
||||||
);
|
);
|
||||||
client.sessionStore.storeEndToEndAccount(pickledAccount);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Failed to decrypt with a new session.
|
// Failed to decrypt with a new session.
|
||||||
} finally {
|
|
||||||
session.free();
|
|
||||||
account.free();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3093,8 +3008,8 @@ MatrixClient.prototype.startClient = function(opts) {
|
|||||||
|
|
||||||
this._clientOpts = opts;
|
this._clientOpts = opts;
|
||||||
|
|
||||||
if (CRYPTO_ENABLED && this.sessionStore !== null) {
|
if (this._olmDevice) {
|
||||||
this.uploadKeys(5);
|
this.uploadKeys(5).done();
|
||||||
}
|
}
|
||||||
|
|
||||||
// periodically poll for turn servers if we support voip
|
// periodically poll for turn servers if we support voip
|
||||||
|
|||||||
@@ -88,10 +88,6 @@ describe("MatrixClient crypto", function() {
|
|||||||
expect(aliClient.deviceKeys.device_id).toEqual(aliDeviceId);
|
expect(aliClient.deviceKeys.device_id).toEqual(aliDeviceId);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
it("should have a curve25519 key", function(done) {
|
|
||||||
expect(aliClient.deviceCurve25519Key).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function bobUploadsKeys() {
|
function bobUploadsKeys() {
|
||||||
|
|||||||
Reference in New Issue
Block a user