You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-29 16:43:09 +03:00
Migrate e2e rooms to crypto store
Doesn't do data migration yet
This commit is contained in:
@@ -42,6 +42,7 @@ const MatrixBaseApis = require("./base-apis");
|
|||||||
const MatrixError = httpApi.MatrixError;
|
const MatrixError = httpApi.MatrixError;
|
||||||
|
|
||||||
import ReEmitter from './ReEmitter';
|
import ReEmitter from './ReEmitter';
|
||||||
|
import RoomList from './crypto/RoomList';
|
||||||
|
|
||||||
const SCROLLBACK_DELAY_MS = 3000;
|
const SCROLLBACK_DELAY_MS = 3000;
|
||||||
let CRYPTO_ENABLED = false;
|
let CRYPTO_ENABLED = false;
|
||||||
@@ -181,6 +182,11 @@ function MatrixClient(opts) {
|
|||||||
if (CRYPTO_ENABLED) {
|
if (CRYPTO_ENABLED) {
|
||||||
this.olmVersion = Crypto.getOlmVersion();
|
this.olmVersion = Crypto.getOlmVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List of what rooms have encryption enabled: separate from crypto because
|
||||||
|
// we still want to know what rooms are encrypted even if crypto is disabled:
|
||||||
|
// we don't want to start sening unencrypted events to them.
|
||||||
|
this._roomList = new RoomList(this._cryptoStore);
|
||||||
}
|
}
|
||||||
utils.inherits(MatrixClient, EventEmitter);
|
utils.inherits(MatrixClient, EventEmitter);
|
||||||
utils.extend(MatrixClient.prototype, MatrixBaseApis.prototype);
|
utils.extend(MatrixClient.prototype, MatrixBaseApis.prototype);
|
||||||
@@ -351,13 +357,6 @@ MatrixClient.prototype.initCrypto = async function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CRYPTO_ENABLED) {
|
|
||||||
throw new Error(
|
|
||||||
`End-to-end encryption not supported in this js-sdk build: did ` +
|
|
||||||
`you remember to load the olm library?`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._sessionStore) {
|
if (!this._sessionStore) {
|
||||||
// this is temporary, the sessionstore is supposed to be going away
|
// this is temporary, the sessionstore is supposed to be going away
|
||||||
throw new Error(`Cannot enable encryption: no sessionStore provided`);
|
throw new Error(`Cannot enable encryption: no sessionStore provided`);
|
||||||
@@ -367,6 +366,16 @@ MatrixClient.prototype.initCrypto = async function() {
|
|||||||
throw new Error(`Cannot enable encryption: no cryptoStore provided`);
|
throw new Error(`Cannot enable encryption: no cryptoStore provided`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialise the list of encrypted rooms (whether or not crypto is enabled)
|
||||||
|
await this._roomList.init();
|
||||||
|
|
||||||
|
if (!CRYPTO_ENABLED) {
|
||||||
|
throw new Error(
|
||||||
|
`End-to-end encryption not supported in this js-sdk build: did ` +
|
||||||
|
`you remember to load the olm library?`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
if (userId === null) {
|
if (userId === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -387,6 +396,7 @@ MatrixClient.prototype.initCrypto = async function() {
|
|||||||
userId, this.deviceId,
|
userId, this.deviceId,
|
||||||
this.store,
|
this.store,
|
||||||
this._cryptoStore,
|
this._cryptoStore,
|
||||||
|
this._roomList,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.reEmitter.reEmit(crypto, [
|
this.reEmitter.reEmit(crypto, [
|
||||||
@@ -646,11 +656,7 @@ MatrixClient.prototype.isRoomEncrypted = function(roomId) {
|
|||||||
// we don't have an m.room.encrypted event, but that might be because
|
// we don't have an m.room.encrypted event, but that might be because
|
||||||
// the server is hiding it from us. Check the store to see if it was
|
// the server is hiding it from us. Check the store to see if it was
|
||||||
// previously encrypted.
|
// previously encrypted.
|
||||||
if (!this._sessionStore) {
|
return this._roomList.isRoomEncrypted(roomId);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Boolean(this._sessionStore.getEndToEndRoom(roomId));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
61
src/crypto/RoomList.js
Normal file
61
src/crypto/RoomList.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module crypto/RoomList
|
||||||
|
*
|
||||||
|
* Manages the list of encrypted rooms
|
||||||
|
*/
|
||||||
|
|
||||||
|
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @alias module:crypto/RoomList
|
||||||
|
*/
|
||||||
|
export default class RoomList {
|
||||||
|
constructor(cryptoStore, sessionStore) {
|
||||||
|
this._cryptoStore = cryptoStore;
|
||||||
|
this._sessionStore = sessionStore;
|
||||||
|
this._roomEncryption = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
await this._cryptoStore.doTxn(
|
||||||
|
'readonly', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
||||||
|
this._cryptoStore.getEndToEndRooms(txn, (result) => {
|
||||||
|
this._roomEncryption = result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoomEncryption(roomId) {
|
||||||
|
return this._roomEncryption[roomId] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isRoomEncrypted(roomId) {
|
||||||
|
return Boolean(this.getRoomEncryption(roomId));
|
||||||
|
}
|
||||||
|
|
||||||
|
async setRoomEncryption(roomId, roomInfo) {
|
||||||
|
this._roomEncryption[roomId] = roomInfo;
|
||||||
|
await this._cryptoStore.doTxn(
|
||||||
|
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
||||||
|
this._cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,15 +59,18 @@ import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
|||||||
*
|
*
|
||||||
* @param {module:crypto/store/base~CryptoStore} cryptoStore
|
* @param {module:crypto/store/base~CryptoStore} cryptoStore
|
||||||
* storage for the crypto layer.
|
* storage for the crypto layer.
|
||||||
|
*
|
||||||
|
* @param {RoomList} roomList An initialised RoomList object
|
||||||
*/
|
*/
|
||||||
function Crypto(baseApis, sessionStore, userId, deviceId,
|
function Crypto(baseApis, sessionStore, userId, deviceId,
|
||||||
clientStore, cryptoStore) {
|
clientStore, cryptoStore, roomList) {
|
||||||
this._baseApis = baseApis;
|
this._baseApis = baseApis;
|
||||||
this._sessionStore = sessionStore;
|
this._sessionStore = sessionStore;
|
||||||
this._userId = userId;
|
this._userId = userId;
|
||||||
this._deviceId = deviceId;
|
this._deviceId = deviceId;
|
||||||
this._clientStore = clientStore;
|
this._clientStore = clientStore;
|
||||||
this._cryptoStore = cryptoStore;
|
this._cryptoStore = cryptoStore;
|
||||||
|
this._roomList = roomList;
|
||||||
|
|
||||||
this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||||
this._deviceList = new DeviceList(
|
this._deviceList = new DeviceList(
|
||||||
@@ -587,6 +590,15 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) {
|
|||||||
return device;
|
return device;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current end-to-end encryption config for a room
|
||||||
|
*
|
||||||
|
* @return {object} The current end-to-end encyption status, or null if
|
||||||
|
* the room is not stored as using end-to-end encryption.
|
||||||
|
*/
|
||||||
|
Crypto.prototype.getRoomEncryption = async function(roomId) {
|
||||||
|
return this._roomList.getRoomEncryption(roomId);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure a room to use encryption (ie, save a flag in the sessionstore).
|
* Configure a room to use encryption (ie, save a flag in the sessionstore).
|
||||||
@@ -601,13 +613,12 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) {
|
|||||||
Crypto.prototype.setRoomEncryption = async function(roomId, config, inhibitDeviceQuery) {
|
Crypto.prototype.setRoomEncryption = async function(roomId, config, inhibitDeviceQuery) {
|
||||||
// if we already have encryption in this room, we should ignore this event
|
// if we already have encryption in this room, we should ignore this event
|
||||||
// (for now at least. maybe we should alert the user somehow?)
|
// (for now at least. maybe we should alert the user somehow?)
|
||||||
const existingConfig = this._sessionStore.getEndToEndRoom(roomId);
|
console.log("arese");
|
||||||
if (existingConfig) {
|
const existingConfig = await this.getRoomEncryption(roomId);
|
||||||
if (JSON.stringify(existingConfig) != JSON.stringify(config)) {
|
if (existingConfig && JSON.stringify(existingConfig) != JSON.stringify(config)) {
|
||||||
console.error("Ignoring m.room.encryption event which requests " +
|
console.error("Ignoring m.room.encryption event which requests " +
|
||||||
"a change of config in " + roomId);
|
"a change of config in " + roomId);
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlgClass = algorithms.ENCRYPTION_CLASSES[config.algorithm];
|
const AlgClass = algorithms.ENCRYPTION_CLASSES[config.algorithm];
|
||||||
@@ -615,7 +626,7 @@ Crypto.prototype.setRoomEncryption = async function(roomId, config, inhibitDevic
|
|||||||
throw new Error("Unable to encrypt with " + config.algorithm);
|
throw new Error("Unable to encrypt with " + config.algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sessionStore.storeEndToEndRoom(roomId, config);
|
await this._roomList.setRoomEncryption(roomId);
|
||||||
|
|
||||||
const alg = new AlgClass({
|
const alg = new AlgClass({
|
||||||
userId: this._userId,
|
userId: this._userId,
|
||||||
@@ -693,16 +704,6 @@ Crypto.prototype.ensureOlmSessionsForUsers = function(users) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether encryption is enabled for a room.
|
|
||||||
* @param {string} roomId the room id to query.
|
|
||||||
* @return {bool} whether encryption is enabled.
|
|
||||||
*/
|
|
||||||
Crypto.prototype.isRoomEncrypted = function(roomId) {
|
|
||||||
return Boolean(this._roomEncryptors[roomId]);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list containing all of the room keys
|
* Get a list containing all of the room keys
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ limitations under the License.
|
|||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import utils from '../../utils';
|
import utils from '../../utils';
|
||||||
|
|
||||||
export const VERSION = 5;
|
export const VERSION = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of a CryptoStore which is backed by an existing
|
* Implementation of a CryptoStore which is backed by an existing
|
||||||
@@ -425,6 +425,30 @@ export class Backend {
|
|||||||
objectStore.put(deviceData, "-");
|
objectStore.put(deviceData, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||||
|
const objectStore = txn.objectStore("rooms");
|
||||||
|
objectStore.put(roomInfo, roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEndToEndRooms(txn, func) {
|
||||||
|
const rooms = {};
|
||||||
|
const objectStore = txn.objectStore("rooms");
|
||||||
|
const getReq = objectStore.openCursor();
|
||||||
|
getReq.onsuccess = function() {
|
||||||
|
const cursor = getReq.result;
|
||||||
|
if (cursor) {
|
||||||
|
rooms[cursor.key] = cursor.value;
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
func(rooms);
|
||||||
|
} catch (e) {
|
||||||
|
abortWithException(txn, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
doTxn(mode, stores, func) {
|
doTxn(mode, stores, func) {
|
||||||
const txn = this._db.transaction(stores, mode);
|
const txn = this._db.transaction(stores, mode);
|
||||||
const promise = promiseifyTxn(txn);
|
const promise = promiseifyTxn(txn);
|
||||||
@@ -460,6 +484,9 @@ export function upgradeDatabase(db, oldVersion) {
|
|||||||
if (oldVersion < 5) {
|
if (oldVersion < 5) {
|
||||||
db.createObjectStore("device_data");
|
db.createObjectStore("device_data");
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 6) {
|
||||||
|
db.createObjectStore("rooms");
|
||||||
|
}
|
||||||
// Expand as needed.
|
// Expand as needed.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -386,6 +386,27 @@ export default class IndexedDBCryptoStore {
|
|||||||
this._backendPromise.value().getEndToEndDeviceData(txn, func);
|
this._backendPromise.value().getEndToEndDeviceData(txn, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// End to End Rooms
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the end-to-end state for a room.
|
||||||
|
* @param {string} roomId The room's ID.
|
||||||
|
* @param {object} roomInfo The end-to-end info for the room.
|
||||||
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
|
*/
|
||||||
|
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||||
|
this._backendPromise.value().storeEndToEndRoom(roomId, roomInfo, txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an object of roomId->roomInfo for all e2e rooms in the store
|
||||||
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
|
* @param {function(Object)} func Function called with the end to end encrypted rooms
|
||||||
|
*/
|
||||||
|
getEndToEndRooms(txn, func) {
|
||||||
|
this._backendPromise.value().getEndToEndRooms(txn, func);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a transaction on the crypto store. Any store methods
|
* Perform a transaction on the crypto store. Any store methods
|
||||||
* that require a transaction (txn) object to be passed in may
|
* that require a transaction (txn) object to be passed in may
|
||||||
@@ -418,3 +439,4 @@ IndexedDBCryptoStore.STORE_ACCOUNT = 'account';
|
|||||||
IndexedDBCryptoStore.STORE_SESSIONS = 'sessions';
|
IndexedDBCryptoStore.STORE_SESSIONS = 'sessions';
|
||||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
|
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
|
||||||
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
|
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
|
||||||
|
IndexedDBCryptoStore.STORE_ROOMS = 'rooms';
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const E2E_PREFIX = "crypto.";
|
|||||||
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
|
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
|
||||||
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
|
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
|
||||||
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
|
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
|
||||||
|
const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/";
|
||||||
|
|
||||||
function keyEndToEndSessions(deviceKey) {
|
function keyEndToEndSessions(deviceKey) {
|
||||||
return E2E_PREFIX + "sessions/" + deviceKey;
|
return E2E_PREFIX + "sessions/" + deviceKey;
|
||||||
@@ -40,6 +41,10 @@ function keyEndToEndInboundGroupSession(senderKey, sessionId) {
|
|||||||
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
|
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function keyEndToEndRoomsPrefix(roomId) {
|
||||||
|
return KEY_ROOMS_PREFIX + roomId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements {module:crypto/store/base~CryptoStore}
|
* @implements {module:crypto/store/base~CryptoStore}
|
||||||
*/
|
*/
|
||||||
@@ -140,6 +145,18 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||||
|
setJsonItem(
|
||||||
|
this.store, keyEndToEndRoomsPrefix(roomId), roomInfo,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEndToEndRoom(roomId, txn, func) {
|
||||||
|
func(getJsonItem(
|
||||||
|
this.store, keyEndToEndRoomsPrefix(roomId),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete all data from this store.
|
* Delete all data from this store.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ export default class MemoryCryptoStore {
|
|||||||
this._inboundGroupSessions = {};
|
this._inboundGroupSessions = {};
|
||||||
// Opaque device data object
|
// Opaque device data object
|
||||||
this._deviceData = null;
|
this._deviceData = null;
|
||||||
|
// roomId -> Opaque roomInfo object
|
||||||
|
this._rooms = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -283,6 +285,15 @@ export default class MemoryCryptoStore {
|
|||||||
this._deviceData = deviceData;
|
this._deviceData = deviceData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// E2E rooms
|
||||||
|
|
||||||
|
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||||
|
this._rooms[roomId] = roomInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEndToEndRoom(roomId, txn, func) {
|
||||||
|
func(this._rooms[roomId]);
|
||||||
|
}
|
||||||
|
|
||||||
doTxn(mode, stores, func) {
|
doTxn(mode, stores, func) {
|
||||||
return Promise.resolve(func(null));
|
return Promise.resolve(func(null));
|
||||||
|
|||||||
Reference in New Issue
Block a user