You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-26 17:03:12 +03:00
Merge branch 'e2e_backups' of git://github.com/uhoreg/matrix-js-sdk into uhoreg-e2e_backups
This commit is contained in:
245
spec/unit/crypto/backup.spec.js
Normal file
245
spec/unit/crypto/backup.spec.js
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 New Vector 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.
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
global.Olm = require('olm');
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("unable to run megolm backup tests: libolm not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
import expect from 'expect';
|
||||||
|
import Promise from 'bluebird';
|
||||||
|
|
||||||
|
import sdk from '../../..';
|
||||||
|
import algorithms from '../../../lib/crypto/algorithms';
|
||||||
|
import WebStorageSessionStore from '../../../lib/store/session/webstorage';
|
||||||
|
import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js';
|
||||||
|
import MockStorageApi from '../../MockStorageApi';
|
||||||
|
import testUtils from '../../test-utils';
|
||||||
|
|
||||||
|
// Crypto and OlmDevice won't import unless we have global.Olm
|
||||||
|
let OlmDevice;
|
||||||
|
let Crypto;
|
||||||
|
if (global.Olm) {
|
||||||
|
OlmDevice = require('../../../lib/crypto/OlmDevice');
|
||||||
|
Crypto = require('../../../lib/crypto');
|
||||||
|
}
|
||||||
|
|
||||||
|
const MatrixClient = sdk.MatrixClient;
|
||||||
|
const MatrixEvent = sdk.MatrixEvent;
|
||||||
|
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||||
|
|
||||||
|
const ROOM_ID = '!ROOM:ID';
|
||||||
|
|
||||||
|
describe("MegolmBackup", function() {
|
||||||
|
if (!global.Olm) {
|
||||||
|
console.warn('Not running megolm backup unit tests: libolm not present');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let olmDevice;
|
||||||
|
let mockOlmLib;
|
||||||
|
let mockCrypto;
|
||||||
|
let mockStorage;
|
||||||
|
let sessionStore;
|
||||||
|
let cryptoStore;
|
||||||
|
let megolmDecryption;
|
||||||
|
beforeEach(function () {
|
||||||
|
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
|
||||||
|
|
||||||
|
mockCrypto = testUtils.mock(Crypto, 'Crypto');
|
||||||
|
mockCrypto.backupKey = new global.Olm.PkEncryption();
|
||||||
|
mockCrypto.backupKey.set_recipient_key(
|
||||||
|
"hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK"
|
||||||
|
);
|
||||||
|
|
||||||
|
mockStorage = new MockStorageApi();
|
||||||
|
sessionStore = new WebStorageSessionStore(mockStorage);
|
||||||
|
cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||||
|
|
||||||
|
olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||||
|
|
||||||
|
// we stub out the olm encryption bits
|
||||||
|
mockOlmLib = {};
|
||||||
|
mockOlmLib.ensureOlmSessionsForDevices = expect.createSpy();
|
||||||
|
mockOlmLib.encryptMessageForDevice =
|
||||||
|
expect.createSpy().andReturn(Promise.resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("backup", function() {
|
||||||
|
let mockBaseApis;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
mockBaseApis = {};
|
||||||
|
|
||||||
|
megolmDecryption = new MegolmDecryption({
|
||||||
|
userId: '@user:id',
|
||||||
|
crypto: mockCrypto,
|
||||||
|
olmDevice: olmDevice,
|
||||||
|
baseApis: mockBaseApis,
|
||||||
|
roomId: ROOM_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('automatically backs up keys', function() {
|
||||||
|
const groupSession = new global.Olm.OutboundGroupSession();
|
||||||
|
groupSession.create();
|
||||||
|
|
||||||
|
// construct a fake decrypted key event via the use of a mocked
|
||||||
|
// 'crypto' implementation.
|
||||||
|
const event = new MatrixEvent({
|
||||||
|
type: 'm.room.encrypted',
|
||||||
|
});
|
||||||
|
const decryptedData = {
|
||||||
|
clearEvent: {
|
||||||
|
type: 'm.room_key',
|
||||||
|
content: {
|
||||||
|
algorithm: 'm.megolm.v1.aes-sha2',
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
session_id: groupSession.session_id(),
|
||||||
|
session_key: groupSession.session_key(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
senderCurve25519Key: "SENDER_CURVE25519",
|
||||||
|
claimedEd25519Key: "SENDER_ED25519",
|
||||||
|
};
|
||||||
|
|
||||||
|
mockCrypto.decryptEvent = function() {
|
||||||
|
return Promise.resolve(decryptedData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sessionId = groupSession.session_id();
|
||||||
|
const cipherText = groupSession.encrypt(JSON.stringify({
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
content: 'testytest',
|
||||||
|
}));
|
||||||
|
const msgevent = new MatrixEvent({
|
||||||
|
type: 'm.room.encrypted',
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
content: {
|
||||||
|
algorithm: 'm.megolm.v1.aes-sha2',
|
||||||
|
sender_key: "SENDER_CURVE25519",
|
||||||
|
session_id: sessionId,
|
||||||
|
ciphertext: cipherText,
|
||||||
|
},
|
||||||
|
event_id: "$event1",
|
||||||
|
origin_server_ts: 1507753886000,
|
||||||
|
});
|
||||||
|
|
||||||
|
mockBaseApis.sendKeyBackup = expect.createSpy();
|
||||||
|
|
||||||
|
return event.attemptDecryption(mockCrypto).then(() => {
|
||||||
|
return megolmDecryption.onRoomKeyEvent(event);
|
||||||
|
}).then(() => {
|
||||||
|
expect(mockBaseApis.sendKeyBackup).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("restore", function () {
|
||||||
|
let client;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
const scheduler = [
|
||||||
|
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
|
||||||
|
"setProcessFunction",
|
||||||
|
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
|
||||||
|
const store = [
|
||||||
|
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
|
||||||
|
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser",
|
||||||
|
"getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter",
|
||||||
|
"getSyncAccumulator", "startup", "deleteAllData",
|
||||||
|
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
|
||||||
|
store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null));
|
||||||
|
store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null));
|
||||||
|
store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null));
|
||||||
|
client = new MatrixClient({
|
||||||
|
baseUrl: "https://my.home.server",
|
||||||
|
idBaseUrl: "https://identity.server",
|
||||||
|
accessToken: "my.access.token",
|
||||||
|
request: function() {}, // NOP
|
||||||
|
store: store,
|
||||||
|
scheduler: scheduler,
|
||||||
|
userId: "@alice:bar",
|
||||||
|
deviceId: "device",
|
||||||
|
sessionStore: sessionStore,
|
||||||
|
cryptoStore: cryptoStore,
|
||||||
|
});
|
||||||
|
|
||||||
|
megolmDecryption = new MegolmDecryption({
|
||||||
|
userId: '@user:id',
|
||||||
|
crypto: mockCrypto,
|
||||||
|
olmDevice: olmDevice,
|
||||||
|
baseApis: client,
|
||||||
|
roomId: ROOM_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
|
|
||||||
|
return client.initCrypto();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can restore from backup', function () {
|
||||||
|
const event = new MatrixEvent({
|
||||||
|
type: 'm.room.encrypted',
|
||||||
|
room_id: '!ROOM:ID',
|
||||||
|
content: {
|
||||||
|
algorithm: 'm.megolm.v1.aes-sha2',
|
||||||
|
sender_key: 'SENDER_CURVE25519',
|
||||||
|
session_id: 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc',
|
||||||
|
ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N'
|
||||||
|
+ 'CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl'
|
||||||
|
+ 'mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs'
|
||||||
|
},
|
||||||
|
event_id: '$event1',
|
||||||
|
origin_server_ts: 1507753886000,
|
||||||
|
});
|
||||||
|
client._http.authedRequest = function () {
|
||||||
|
return Promise.resolve({
|
||||||
|
first_message_index: 0,
|
||||||
|
forwarded_count: 0,
|
||||||
|
is_verified: false,
|
||||||
|
session_data: {
|
||||||
|
ciphertext: '2z2M7CZ+azAiTHN1oFzZ3smAFFt+LEOYY6h3QO3XXGdw'
|
||||||
|
+ '6YpNn/gpHDO6I/rgj1zNd4FoTmzcQgvKdU8kN20u5BWRHxaHTZ'
|
||||||
|
+ 'Slne5RxE6vUdREsBgZePglBNyG0AogR/PVdcrv/v18Y6rLM5O9'
|
||||||
|
+ 'SELmwbV63uV9Kuu/misMxoqbuqEdG7uujyaEKtjlQsJ5MGPQOy'
|
||||||
|
+ 'Syw7XrnesSwF6XWRMxcPGRV0xZr3s9PI350Wve3EncjRgJ9IGF'
|
||||||
|
+ 'ru1bcptMqfXgPZkOyGvrphHoFfoK7nY3xMEHUiaTRfRIjq8HNV'
|
||||||
|
+ '4o8QY1qmWGnxNBQgOlL8MZlykjg3ULmQ3DtFfQPj/YYGS3jzxv'
|
||||||
|
+ 'C+EBjaafmsg+52CTeK3Rswu72PX450BnSZ1i3If4xWAUKvjTpe'
|
||||||
|
+ 'Ug5aDLqttOv1pITolTJDw5W/SD+b5rjEKg1CFCHGEGE9wwV3Nf'
|
||||||
|
+ 'QHVCQL+dfpd7Or0poy4dqKMAi3g0o3Tg7edIF8d5rREmxaALPy'
|
||||||
|
+ 'iie8PHD8mj/5Y0GLqrac4CD6+Mop7eUTzVovprjg',
|
||||||
|
mac: '5lxYBHQU80M',
|
||||||
|
ephemeral: '/Bn0A4UMFwJaDDvh0aEk1XZj3k1IfgCxgFY9P9a0b14',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return client.restoreKeyBackups(
|
||||||
|
"qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD"
|
||||||
|
+ "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA",
|
||||||
|
ROOM_ID,
|
||||||
|
'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc'
|
||||||
|
).then(() => {
|
||||||
|
return megolmDecryption.decryptEvent(event);
|
||||||
|
}).then((res) => {
|
||||||
|
expect(res.clearEvent.content).toEqual('testytest');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
177
src/client.js
177
src/client.js
@@ -732,6 +732,177 @@ MatrixClient.prototype.importRoomKeys = function(keys) {
|
|||||||
return this._crypto.importRoomKeys(keys);
|
return this._crypto.importRoomKeys(keys);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get information about the current key backup.
|
||||||
|
*/
|
||||||
|
MatrixClient.prototype.getKeyBackupVersion = function(callback) {
|
||||||
|
if (this._crypto === null) {
|
||||||
|
throw new Error("End-to-end encryption disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._http.authedRequest(
|
||||||
|
undefined, "GET", "/room_keys/version",
|
||||||
|
).then((res) => {
|
||||||
|
if (res.algorithm !== olmlib.MEGOLM_BACKUP_ALGORITHM) {
|
||||||
|
const err = "Unknown backup algorithm: " + res.algorithm;
|
||||||
|
callback(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
} else if (!(typeof res.auth_data === "object")
|
||||||
|
|| !res.auth_data.public_key) {
|
||||||
|
const err = "Invalid backup data returned";
|
||||||
|
callback(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
} else {
|
||||||
|
if (callback) {
|
||||||
|
callback(null, res);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable backing up of keys, using data previously returned from
|
||||||
|
* getKeyBackupVersion.
|
||||||
|
*/
|
||||||
|
MatrixClient.prototype.enableKeyBackup = function(info) {
|
||||||
|
if (this._crypto === null) {
|
||||||
|
throw new Error("End-to-end encryption disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
this._crypto.backupKey = new global.Olm.PkEncryption();
|
||||||
|
this._crypto.backupKey.set_recipient_key(info.auth_data.public_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable backing up of keys.
|
||||||
|
*/
|
||||||
|
MatrixClient.prototype.disableKeyBackup = function() {
|
||||||
|
if (this._crypto === null) {
|
||||||
|
throw new Error("End-to-end encryption disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
this._crypto.backupKey = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new key backup version and enable it.
|
||||||
|
*/
|
||||||
|
MatrixClient.prototype.createKeyBackupVersion = function(callback) {
|
||||||
|
if (this._crypto === null) {
|
||||||
|
throw new Error("End-to-end encryption disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
const decryption = new global.Olm.PkDecryption();
|
||||||
|
const public_key = decryption.generate_key();
|
||||||
|
const encryption = new global.Olm.PkEncryption();
|
||||||
|
encryption.set_recipient_key(public_key);
|
||||||
|
const data = {
|
||||||
|
algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM,
|
||||||
|
auth_data: {
|
||||||
|
public_key: public_key,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._crypto._signObject(data.auth_data);
|
||||||
|
return this._http.authedRequest(
|
||||||
|
undefined, "POST", "/room_keys/version", undefined, data,
|
||||||
|
).then((res) => {
|
||||||
|
this._crypto.backupKey = encryption;
|
||||||
|
// FIXME: pickle isn't the right thing to use, but we don't have
|
||||||
|
// anything else yet
|
||||||
|
const recovery_key = decryption.pickle("secret_key");
|
||||||
|
callback(null, recovery_key);
|
||||||
|
return recovery_key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixClient.prototype._makeKeyBackupPath = function(roomId, sessionId, version) {
|
||||||
|
let path;
|
||||||
|
if (sessionId !== undefined) {
|
||||||
|
path = utils.encodeUri("/room_keys/keys/$roomId/$sessionId", {
|
||||||
|
$roomId: roomId,
|
||||||
|
$sessionId: sessionId,
|
||||||
|
});
|
||||||
|
} else if (roomId !== undefined) {
|
||||||
|
path = utils.encodeUri("/room_keys/keys/$roomId", {
|
||||||
|
$roomId: roomId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
path = "/room_keys/keys";
|
||||||
|
}
|
||||||
|
const queryData = version === undefined ? undefined : {version : version};
|
||||||
|
return {
|
||||||
|
path: path,
|
||||||
|
queryData: queryData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Back up session keys to the homeserver.
|
||||||
|
* @param {string} roomId ID of the room that the keys are for Optional.
|
||||||
|
* @param {string} sessionId ID of the session that the keys are for Optional.
|
||||||
|
* @param {integer} version backup version Optional.
|
||||||
|
* @param {object} key data
|
||||||
|
* @param {module:client.callback} callback Optional.
|
||||||
|
* @return {module:client.Promise} a promise that will resolve when the keys
|
||||||
|
* are uploaded
|
||||||
|
*/
|
||||||
|
MatrixClient.prototype.sendKeyBackup = function(roomId, sessionId, version, data, callback) {
|
||||||
|
if (this._crypto === null) {
|
||||||
|
throw new Error("End-to-end encryption disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = this._makeKeyBackupPath(roomId, sessionId, version);
|
||||||
|
return this._http.authedRequest(
|
||||||
|
callback, "PUT", path.path, path.queryData, data,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessionId, version, callback) {
|
||||||
|
if (this._crypto === null) {
|
||||||
|
throw new Error("End-to-end encryption disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: see the FIXME in createKeyBackupVersion
|
||||||
|
const decryption = new global.Olm.PkDecryption();
|
||||||
|
decryption.unpickle("secret_key", decryptionKey);
|
||||||
|
|
||||||
|
const path = this._makeKeyBackupPath(roomId, sessionId, version);
|
||||||
|
return this._http.authedRequest(
|
||||||
|
undefined, "GET", path.path, path.queryData,
|
||||||
|
).then((res) => {
|
||||||
|
const keys = [];
|
||||||
|
// FIXME: for each room, session, if response has multiple
|
||||||
|
// decrypt response.data.session_data
|
||||||
|
const session_data = res.session_data;
|
||||||
|
const key = JSON.parse(decryption.decrypt(
|
||||||
|
session_data.ephemeral,
|
||||||
|
session_data.mac,
|
||||||
|
session_data.ciphertext
|
||||||
|
));
|
||||||
|
// set room_id and session_id
|
||||||
|
key.room_id = roomId;
|
||||||
|
key.session_id = sessionId;
|
||||||
|
keys.push(key);
|
||||||
|
return this.importRoomKeys(keys);
|
||||||
|
}).then(() => {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
MatrixClient.prototype.deleteKeyBackups = function(roomId, sessionId, version, callback) {
|
||||||
|
if (this._crypto === null) {
|
||||||
|
throw new Error("End-to-end encryption disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = this._makeKeyBackupPath(roomId, sessionId, version);
|
||||||
|
return this._http.authedRequest(
|
||||||
|
callback, "DELETE", path.path, path.queryData,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
// Group ops
|
// Group ops
|
||||||
// =========
|
// =========
|
||||||
// Operations on groups that come down the sync stream (ie. ones the
|
// Operations on groups that come down the sync stream (ie. ones the
|
||||||
@@ -3695,6 +3866,12 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires when we want to suggest to the user that they restore their megolm keys
|
||||||
|
* from backup or by cross-signing the device.
|
||||||
|
*
|
||||||
|
* @event module:client~MatrixClient#"crypto.suggestKeyRestore"
|
||||||
|
*/
|
||||||
|
|
||||||
// EventEmitter JSDocs
|
// EventEmitter JSDocs
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,17 @@ function OlmDevice(sessionStore, cryptoStore) {
|
|||||||
this.deviceEd25519Key = null;
|
this.deviceEd25519Key = null;
|
||||||
this._maxOneTimeKeys = null;
|
this._maxOneTimeKeys = null;
|
||||||
|
|
||||||
|
// track which of our other devices (if any) have cross-signed this device
|
||||||
|
// XXX: this should probably have a single source of truth in the /devices
|
||||||
|
// API store or whatever we use to track our self-signed devices.
|
||||||
|
this.crossSelfSigs = [];
|
||||||
|
|
||||||
|
// track whether we have already suggested to the user that they should
|
||||||
|
// restore their keys from backup or by cross-signing the device.
|
||||||
|
// We use this to avoid repeatedly emitting the suggestion event.
|
||||||
|
// XXX: persist this somewhere!
|
||||||
|
this.suggestedKeyRestore = false;
|
||||||
|
|
||||||
// we don't bother stashing outboundgroupsessions in the sessionstore -
|
// we don't bother stashing outboundgroupsessions in the sessionstore -
|
||||||
// instead we keep them here.
|
// instead we keep them here.
|
||||||
this._outboundGroupSessionStore = {};
|
this._outboundGroupSessionStore = {};
|
||||||
|
|||||||
@@ -814,7 +814,7 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Adding key for megolm session ${senderKey}|${sessionId}`);
|
console.log(`Adding key for megolm session ${senderKey}|${sessionId}`);
|
||||||
this._olmDevice.addInboundGroupSession(
|
return this._olmDevice.addInboundGroupSession(
|
||||||
content.room_id, senderKey, forwardingKeyChain, sessionId,
|
content.room_id, senderKey, forwardingKeyChain, sessionId,
|
||||||
content.session_key, keysClaimed,
|
content.session_key, keysClaimed,
|
||||||
exportFormat,
|
exportFormat,
|
||||||
@@ -829,6 +829,12 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
|
|||||||
|
|
||||||
// have another go at decrypting events sent with this session.
|
// have another go at decrypting events sent with this session.
|
||||||
this._retryDecryption(senderKey, sessionId);
|
this._retryDecryption(senderKey, sessionId);
|
||||||
|
}).then(() => {
|
||||||
|
return this.backupGroupSession(
|
||||||
|
content.room_id, senderKey, forwardingKeyChain,
|
||||||
|
content.session_id, content.session_key, keysClaimed,
|
||||||
|
exportFormat,
|
||||||
|
);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
console.error(`Error handling m.room_key_event: ${e}`);
|
console.error(`Error handling m.room_key_event: ${e}`);
|
||||||
});
|
});
|
||||||
@@ -951,6 +957,54 @@ MegolmDecryption.prototype.importRoomKey = function(session) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MegolmDecryption.prototype.backupGroupSession = async function(
|
||||||
|
roomId, senderKey, forwardingCurve25519KeyChain,
|
||||||
|
sessionId, sessionKey, keysClaimed,
|
||||||
|
exportFormat,
|
||||||
|
) {
|
||||||
|
// new session.
|
||||||
|
const session = new Olm.InboundGroupSession();
|
||||||
|
let first_known_index;
|
||||||
|
try {
|
||||||
|
if (exportFormat) {
|
||||||
|
session.import_session(sessionKey);
|
||||||
|
} else {
|
||||||
|
session.create(sessionKey);
|
||||||
|
}
|
||||||
|
if (sessionId != session.session_id()) {
|
||||||
|
throw new Error(
|
||||||
|
"Mismatched group session ID from senderKey: " +
|
||||||
|
senderKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exportFormat) {
|
||||||
|
sessionKey = session.export_session();
|
||||||
|
}
|
||||||
|
const first_known_index = session.first_known_index();
|
||||||
|
|
||||||
|
const sessionData = {
|
||||||
|
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||||
|
sender_key: senderKey,
|
||||||
|
sender_claimed_keys: keysClaimed,
|
||||||
|
forwardingCurve25519KeyChain: forwardingCurve25519KeyChain,
|
||||||
|
session_key: sessionKey
|
||||||
|
};
|
||||||
|
const encrypted = this._crypto.backupKey.encrypt(JSON.stringify(sessionData));
|
||||||
|
const data = {
|
||||||
|
first_message_index: first_known_index,
|
||||||
|
forwarded_count: forwardingCurve25519KeyChain.length,
|
||||||
|
is_verified: false, // FIXME: how do we determine this?
|
||||||
|
session_data: encrypted
|
||||||
|
};
|
||||||
|
return this._baseApis.sendKeyBackup(roomId, sessionId, data);
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
} finally {
|
||||||
|
session.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Have another go at decrypting events after we receive a key
|
* Have another go at decrypting events after we receive a key
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ function Crypto(baseApis, sessionStore, userId, deviceId,
|
|||||||
this._cryptoStore = cryptoStore;
|
this._cryptoStore = cryptoStore;
|
||||||
this._roomList = roomList;
|
this._roomList = roomList;
|
||||||
|
|
||||||
|
// track whether this device's megolm keys are being backed up incrementally
|
||||||
|
// to the server or not.
|
||||||
|
// XXX: this should probably have a single source of truth from OlmAccount
|
||||||
|
this.backupKey = null;
|
||||||
|
|
||||||
this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||||
this._deviceList = new DeviceList(
|
this._deviceList = new DeviceList(
|
||||||
baseApis, cryptoStore, sessionStore, this._olmDevice,
|
baseApis, cryptoStore, sessionStore, this._olmDevice,
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ module.exports.OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
|
|||||||
*/
|
*/
|
||||||
module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
|
module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* matrix algorithm tag for megolm backups
|
||||||
|
*/
|
||||||
|
module.exports.MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt an event payload for an Olm device
|
* Encrypt an event payload for an Olm device
|
||||||
|
|||||||
11
src/sync.js
11
src/sync.js
@@ -1088,6 +1088,17 @@ SyncApi.prototype._processSyncResponse = async function(
|
|||||||
async function processRoomEvent(e) {
|
async function processRoomEvent(e) {
|
||||||
client.emit("event", e);
|
client.emit("event", e);
|
||||||
if (e.isState() && e.getType() == "m.room.encryption" && self.opts.crypto) {
|
if (e.isState() && e.getType() == "m.room.encryption" && self.opts.crypto) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
// XXX: get device
|
||||||
|
if (!device.getSuggestedKeyRestore() &&
|
||||||
|
!device.backupKey && !device.selfCrossSigs.length)
|
||||||
|
{
|
||||||
|
client.emit("crypto.suggestKeyRestore");
|
||||||
|
device.setSuggestedKeyRestore(true);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
await self.opts.crypto.onCryptoEvent(e);
|
await self.opts.crypto.onCryptoEvent(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user