You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
Restart broken Olm sessions
* Start a new Olm sessions with a device when we get an undecryptable message on it. * Send a dummy message on that sessions such that the other end knows about it. * Re-send any outstanding keyshare requests for that device. Also includes a unit test for megolm that isn't very related but came out as a result anyway. Includes https://github.com/matrix-org/matrix-js-sdk/pull/776 Fixes https://github.com/vector-im/riot-web/issues/3822
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
|
||||
"use strict";
|
||||
import 'source-map-support/register';
|
||||
import Crypto from '../../lib/crypto';
|
||||
import expect from 'expect';
|
||||
|
||||
import WebStorageSessionStore from '../../lib/store/session/webstorage';
|
||||
import MemoryCryptoStore from '../../lib/crypto/store/memory-crypto-store.js';
|
||||
import MockStorageApi from '../MockStorageApi';
|
||||
|
||||
const EventEmitter = require("events").EventEmitter;
|
||||
|
||||
const sdk = require("../..");
|
||||
|
||||
const Olm = global.Olm;
|
||||
@@ -20,4 +24,96 @@ describe("Crypto", function() {
|
||||
it("Crypto exposes the correct olm library version", function() {
|
||||
expect(Crypto.getOlmVersion()[0]).toEqual(3);
|
||||
});
|
||||
|
||||
|
||||
describe('Session management', function() {
|
||||
const otkResponse = {
|
||||
one_time_keys: {
|
||||
'@alice:home.server': {
|
||||
aliceDevice: {
|
||||
'signed_curve25519:FLIBBLE': {
|
||||
key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
|
||||
signatures: {
|
||||
'@alice:home.server': {
|
||||
'ed25519:aliceDevice': 'totally a valid signature',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
let crypto;
|
||||
let mockBaseApis;
|
||||
let mockRoomList;
|
||||
|
||||
let fakeEmitter;
|
||||
|
||||
beforeEach(async function() {
|
||||
const mockStorage = new MockStorageApi();
|
||||
const sessionStore = new WebStorageSessionStore(mockStorage);
|
||||
const cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||
|
||||
cryptoStore.storeEndToEndDeviceData({
|
||||
devices: {
|
||||
'@bob:home.server': {
|
||||
'BOBDEVICE': {
|
||||
keys: {
|
||||
'curve25519:BOBDEVICE': 'this is a key',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
trackingStatus: {},
|
||||
});
|
||||
|
||||
mockBaseApis = {
|
||||
sendToDevice: expect.createSpy(),
|
||||
};
|
||||
mockRoomList = {};
|
||||
|
||||
fakeEmitter = new EventEmitter();
|
||||
|
||||
crypto = new Crypto(
|
||||
mockBaseApis,
|
||||
sessionStore,
|
||||
"@alice:home.server",
|
||||
"FLIBBLE",
|
||||
sessionStore,
|
||||
cryptoStore,
|
||||
mockRoomList,
|
||||
);
|
||||
crypto.registerEventHandlers(fakeEmitter);
|
||||
await crypto.init();
|
||||
});
|
||||
|
||||
afterEach(async function() {
|
||||
await crypto.stop();
|
||||
});
|
||||
|
||||
it("restarts wedged Olm sessions", async function() {
|
||||
const prom = new Promise((resolve) => {
|
||||
mockBaseApis.claimOneTimeKeys = function() {
|
||||
resolve();
|
||||
return otkResponse;
|
||||
};
|
||||
});
|
||||
|
||||
fakeEmitter.emit('toDeviceEvent', {
|
||||
getType: expect.createSpy().andReturn('m.room.message'),
|
||||
getContent: expect.createSpy().andReturn({
|
||||
msgtype: 'm.bad.encrypted',
|
||||
}),
|
||||
getWireContent: expect.createSpy().andReturn({
|
||||
algorithm: 'm.olm.v1.curve25519-aes-sha2',
|
||||
sender_key: 'this is a key',
|
||||
}),
|
||||
getSender: expect.createSpy().andReturn('@bob:home.server'),
|
||||
});
|
||||
|
||||
console.log("waiting");
|
||||
await prom;
|
||||
console.log("done");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -18,6 +18,7 @@ import Crypto from '../../../../lib/crypto';
|
||||
|
||||
const MatrixEvent = sdk.MatrixEvent;
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||
|
||||
const ROOM_ID = '!ROOM:ID';
|
||||
|
||||
@@ -34,9 +35,11 @@ describe("MegolmDecryption", function() {
|
||||
let mockCrypto;
|
||||
let mockBaseApis;
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(async function() {
|
||||
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
|
||||
|
||||
await Olm.init();
|
||||
|
||||
mockCrypto = testUtils.mock(Crypto, 'Crypto');
|
||||
mockBaseApis = {};
|
||||
|
||||
@@ -66,7 +69,6 @@ describe("MegolmDecryption", function() {
|
||||
describe('receives some keys:', function() {
|
||||
let groupSession;
|
||||
beforeEach(async function() {
|
||||
await Olm.init();
|
||||
groupSession = new global.Olm.OutboundGroupSession();
|
||||
groupSession.create();
|
||||
|
||||
@@ -263,5 +265,92 @@ describe("MegolmDecryption", function() {
|
||||
// test is successful if no exception is thrown
|
||||
});
|
||||
});
|
||||
|
||||
it("re-uses sessions for sequential messages", async function() {
|
||||
const mockStorage = new MockStorageApi();
|
||||
const sessionStore = new WebStorageSessionStore(mockStorage);
|
||||
const cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||
|
||||
const olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||
olmDevice.verifySignature = expect.createSpy();
|
||||
await olmDevice.init();
|
||||
|
||||
mockBaseApis.claimOneTimeKeys = expect.createSpy().andReturn(Promise.resolve({
|
||||
one_time_keys: {
|
||||
'@alice:home.server': {
|
||||
aliceDevice: {
|
||||
'signed_curve25519:flooble': {
|
||||
key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
|
||||
signatures: {
|
||||
'@alice:home.server': {
|
||||
'ed25519:aliceDevice': 'totally valid',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
mockBaseApis.sendToDevice = expect.createSpy().andReturn(Promise.resolve());
|
||||
|
||||
mockCrypto.downloadKeys.andReturn(Promise.resolve({
|
||||
'@alice:home.server': {
|
||||
aliceDevice: {
|
||||
deviceId: 'aliceDevice',
|
||||
isBlocked: expect.createSpy().andReturn(false),
|
||||
isUnverified: expect.createSpy().andReturn(false),
|
||||
getIdentityKey: expect.createSpy().andReturn(
|
||||
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
|
||||
),
|
||||
getFingerprint: expect.createSpy().andReturn(''),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const megolmEncryption = new MegolmEncryption({
|
||||
userId: '@user:id',
|
||||
crypto: mockCrypto,
|
||||
olmDevice: olmDevice,
|
||||
baseApis: mockBaseApis,
|
||||
roomId: ROOM_ID,
|
||||
config: {
|
||||
rotation_period_ms: 9999999999999,
|
||||
},
|
||||
});
|
||||
const mockRoom = {
|
||||
getEncryptionTargetMembers: expect.createSpy().andReturn(
|
||||
[{userId: "@alice:home.server"}],
|
||||
),
|
||||
getBlacklistUnverifiedDevices: expect.createSpy().andReturn(false),
|
||||
};
|
||||
const ct1 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
|
||||
body: "Some text",
|
||||
});
|
||||
expect(mockRoom.getEncryptionTargetMembers).toHaveBeenCalled();
|
||||
|
||||
// this should have claimed a key for alice as it's starting a new session
|
||||
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalled(
|
||||
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519',
|
||||
);
|
||||
expect(mockCrypto.downloadKeys).toHaveBeenCalledWith(
|
||||
['@alice:home.server'], false,
|
||||
);
|
||||
expect(mockBaseApis.sendToDevice).toHaveBeenCalled();
|
||||
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalled(
|
||||
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519',
|
||||
);
|
||||
|
||||
mockBaseApis.claimOneTimeKeys.reset();
|
||||
|
||||
const ct2 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
|
||||
body: "Some more text",
|
||||
});
|
||||
|
||||
// this should *not* have claimed a key as it should be using the same session
|
||||
expect(mockBaseApis.claimOneTimeKeys).toNotHaveBeenCalled();
|
||||
|
||||
// likewise they should show the same session ID
|
||||
expect(ct2.session_id).toEqual(ct1.session_id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -461,6 +461,12 @@ OlmDevice.prototype.createOutboundSession = async function(
|
||||
session.create_outbound(account, theirIdentityKey, theirOneTimeKey);
|
||||
newSessionId = session.session_id();
|
||||
this._storeAccount(txn, account);
|
||||
// Pretend we've received a message at this point, otherwise
|
||||
// if we try to send a message to the device, it won't use
|
||||
// this session (storing the creation time separately would
|
||||
// make the pickle longer and would not be useful otherwise).
|
||||
session.set_last_received_message_ts(Date.now());
|
||||
|
||||
this._saveSession(theirIdentityKey, session, txn);
|
||||
} finally {
|
||||
session.free();
|
||||
@@ -725,7 +731,7 @@ OlmDevice.prototype._saveOutboundGroupSession = function(session) {
|
||||
*/
|
||||
OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) {
|
||||
const pickled = this._outboundGroupSessionStore[sessionId];
|
||||
if (pickled === null) {
|
||||
if (pickled === undefined) {
|
||||
throw new Error("Unknown outbound group session " + sessionId);
|
||||
}
|
||||
|
||||
@@ -1059,6 +1065,8 @@ OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, se
|
||||
* @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 {integer} chainIndex The chain index at which to export the session.
|
||||
* If omitted, export at the first index we know about.
|
||||
*
|
||||
* @returns {Promise<{chain_index: number, key: string,
|
||||
* forwarding_curve25519_key_chain: Array<string>,
|
||||
@@ -1066,9 +1074,12 @@ OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, se
|
||||
* }>}
|
||||
* details of the session key. The key is a base64-encoded megolm key in
|
||||
* export format.
|
||||
*
|
||||
* @throws Error If the given chain index could not be obtained from the known
|
||||
* index (ie. the given chain index is before the first we have).
|
||||
*/
|
||||
OlmDevice.prototype.getInboundGroupSessionKey = async function(
|
||||
roomId, senderKey, sessionId,
|
||||
roomId, senderKey, sessionId, chainIndex,
|
||||
) {
|
||||
let result;
|
||||
await this._cryptoStore.doTxn(
|
||||
@@ -1079,14 +1090,19 @@ OlmDevice.prototype.getInboundGroupSessionKey = async function(
|
||||
result = null;
|
||||
return;
|
||||
}
|
||||
const messageIndex = session.first_known_index();
|
||||
|
||||
if (chainIndex === undefined) {
|
||||
chainIndex = session.first_known_index();
|
||||
}
|
||||
|
||||
const exportedSession = session.export_session(chainIndex);
|
||||
|
||||
const claimedKeys = sessionData.keysClaimed || {};
|
||||
const senderEd25519Key = claimedKeys.ed25519 || null;
|
||||
|
||||
result = {
|
||||
"chain_index": messageIndex,
|
||||
"key": session.export_session(messageIndex),
|
||||
"chain_index": chainIndex,
|
||||
"key": exportedSession,
|
||||
"forwarding_curve25519_key_chain":
|
||||
sessionData.forwardingCurve25519KeyChain || [],
|
||||
"sender_claimed_ed25519_key": senderEd25519Key,
|
||||
|
@@ -244,6 +244,21 @@ export default class OutgoingRoomKeyRequestManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for room key requests by target device and state
|
||||
*
|
||||
* @param {string} userId Target user ID
|
||||
* @param {string} deviceId Target device ID
|
||||
*
|
||||
* @return {Promise} resolves to a list of all the
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
|
||||
*/
|
||||
getOutgoingSentRoomKeyRequest(userId, deviceId) {
|
||||
return this._cryptoStore.getOutgoingRoomKeyRequestsByTarget(
|
||||
userId, deviceId, [ROOM_KEY_REQUEST_STATES.SENT],
|
||||
);
|
||||
}
|
||||
|
||||
// start the background timer to send queued requests, if the timer isn't
|
||||
// already running
|
||||
_startTimer() {
|
||||
|
@@ -144,6 +144,11 @@ function MegolmEncryption(params) {
|
||||
// room).
|
||||
this._setupPromise = Promise.resolve();
|
||||
|
||||
// Map of outbound sessions by sessions ID. Used if we need a particular
|
||||
// session (the session we're currently using to send is always obtained
|
||||
// using _setupPromise).
|
||||
this._outboundSessions = {};
|
||||
|
||||
// default rotation periods
|
||||
this._sessionRotationPeriodMsgs = 100;
|
||||
this._sessionRotationPeriodMs = 7 * 24 * 3600 * 1000;
|
||||
@@ -195,6 +200,7 @@ MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
|
||||
if (!session) {
|
||||
logger.log(`Starting new megolm session for room ${self._roomId}`);
|
||||
session = await self._prepareNewSession();
|
||||
self._outboundSessions[session.sessionId] = session;
|
||||
}
|
||||
|
||||
// now check if we need to share with any devices
|
||||
@@ -409,8 +415,87 @@ MegolmEncryption.prototype._encryptAndSendKeysToDevices = function(
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Re-shares a megolm session key with devices if the key has already been
|
||||
* sent to them.
|
||||
*
|
||||
* @param {string} senderKey The key of the originating device for the session
|
||||
* @param {string} sessionId ID of the outbound session to share
|
||||
* @param {string} userId ID of the user who owns the target device
|
||||
* @param {module:crypto/deviceinfo} device The target device
|
||||
*/
|
||||
MegolmEncryption.prototype.reshareKeyWithDevice = async function(
|
||||
senderKey, sessionId, userId, device,
|
||||
) {
|
||||
const obSessionInfo = this._outboundSessions[sessionId];
|
||||
if (!obSessionInfo) {
|
||||
logger.debug("Session ID " + sessionId + " not found: not re-sharing keys");
|
||||
return;
|
||||
}
|
||||
|
||||
// The chain index of the key we previously sent this device
|
||||
const sentChainIndex = obSessionInfo.sharedWithDevices[userId][device.deviceId];
|
||||
|
||||
// get the key from the inbound session: the outbound one will already
|
||||
// have been ratcheted to the next chain index.
|
||||
const key = await this._olmDevice.getInboundGroupSessionKey(
|
||||
this._roomId, senderKey, sessionId, sentChainIndex,
|
||||
);
|
||||
|
||||
if (!key) {
|
||||
logger.warn(
|
||||
"No outbound session key found for " + sessionId + ": not re-sharing keys",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await olmlib.ensureOlmSessionsForDevices(
|
||||
this._olmDevice, this._baseApis, {
|
||||
[userId]: {
|
||||
[device.deviceId]: device,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const payload = {
|
||||
type: "m.forwarded_room_key",
|
||||
content: {
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
room_id: this._roomId,
|
||||
session_id: sessionId,
|
||||
session_key: key.key,
|
||||
chain_index: key.chain_index,
|
||||
sender_key: senderKey,
|
||||
sender_claimed_ed25519_key: key.sender_claimed_ed25519_key,
|
||||
forwarding_curve25519_key_chain: key.forwarding_curve25519_key_chain,
|
||||
},
|
||||
};
|
||||
|
||||
const encryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||
ciphertext: {},
|
||||
};
|
||||
await olmlib.encryptMessageForDevice(
|
||||
encryptedContent.ciphertext,
|
||||
this._userId,
|
||||
this._deviceId,
|
||||
this._olmDevice,
|
||||
userId,
|
||||
device,
|
||||
payload,
|
||||
),
|
||||
|
||||
await this._baseApis.sendToDevice("m.room.encrypted", {
|
||||
[userId]: {
|
||||
[device.deviceId]: encryptedContent,
|
||||
},
|
||||
});
|
||||
logger.debug(
|
||||
`Re-shared key for session ${sessionId} with {userId}:{device.deviceId}`,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
|
||||
*
|
||||
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
||||
|
@@ -1133,6 +1133,8 @@ Crypto.prototype._onToDeviceEvent = function(event) {
|
||||
this._onRoomKeyEvent(event);
|
||||
} else if (event.getType() == "m.room_key_request") {
|
||||
this._onRoomKeyRequestEvent(event);
|
||||
} else if (event.getContent().msgtype === "m.bad.encrypted") {
|
||||
this._onToDeviceBadEncrypted(event);
|
||||
} else if (event.isBeingDecrypted()) {
|
||||
// once the event has been decrypted, try again
|
||||
event.once('Event.decrypted', (ev) => {
|
||||
@@ -1162,6 +1164,74 @@ Crypto.prototype._onRoomKeyEvent = function(event) {
|
||||
alg.onRoomKeyEvent(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a toDevice event that couldn't be decrypted
|
||||
*
|
||||
* @private
|
||||
* @param {module:models/event.MatrixEvent} event undecryptable event
|
||||
*/
|
||||
Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
|
||||
const content = event.getWireContent();
|
||||
const sender = event.getSender();
|
||||
const algorithm = content.algorithm;
|
||||
const deviceKey = content.sender_key;
|
||||
|
||||
if (sender === undefined || deviceKey === undefined || deviceKey === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// establish a new olm session with this device since we're failing to decrypt messages
|
||||
// on a current session.
|
||||
// Note that an undecryptable message from another device could easily be spoofed -
|
||||
// is there anything we can do to mitigate this?
|
||||
const device = this._deviceList.getDeviceByIdentityKey(sender, algorithm, deviceKey);
|
||||
const devicesByUser = {};
|
||||
devicesByUser[sender] = [device];
|
||||
await olmlib.ensureOlmSessionsForDevices(
|
||||
this._olmDevice, this._baseApis, devicesByUser, true,
|
||||
);
|
||||
|
||||
// Now send a blank message on that session so the other side knows about it.
|
||||
// (The keyshare request is sent in the clear so that won't do)
|
||||
// We send this first such that, as long as the toDevice messages arrive in the
|
||||
// same order we sent them, the other end will get this first, set up the new session,
|
||||
// then get the keyshare request and send the key over this new session (because it
|
||||
// it the session it has most recently received a message on).
|
||||
const encryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||
ciphertext: {},
|
||||
};
|
||||
await olmlib.encryptMessageForDevice(
|
||||
encryptedContent.ciphertext,
|
||||
this._userId,
|
||||
this._deviceId,
|
||||
this._olmDevice,
|
||||
sender,
|
||||
device,
|
||||
{type: "m.dummy"},
|
||||
);
|
||||
|
||||
await this._baseApis.sendToDevice("m.room.encrypted", {
|
||||
[sender]: {
|
||||
[device.deviceId]: encryptedContent,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// Most of the time this probably won't be necessary since we'll have queued up a key request when
|
||||
// we failed to decrypt the message and will be waiting a bit for the key to arrive before sending
|
||||
// it. This won't always be the case though so we need to re-send any that have already been sent
|
||||
// to avoid races.
|
||||
const requestsToResend =
|
||||
await this._outgoingRoomKeyRequestManager.getOutgoingSentRoomKeyRequest(
|
||||
sender, device.deviceId,
|
||||
);
|
||||
for (const keyReq of requestsToResend) {
|
||||
this.cancelRoomKeyRequest(keyReq.requestBody, true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a change in the membership state of a member of a room
|
||||
*
|
||||
@@ -1287,9 +1357,27 @@ Crypto.prototype._processReceivedRoomKeyRequest = async function(req) {
|
||||
` for ${roomId} / ${body.session_id} (id ${req.requestId})`);
|
||||
|
||||
if (userId !== this._userId) {
|
||||
// TODO: determine if we sent this device the keys already: in
|
||||
// which case we can do so again.
|
||||
logger.log("Ignoring room key request from other user for now");
|
||||
if (!this._roomEncryptors[roomId]) {
|
||||
logger.debug(`room key request for unencrypted room ${roomId}`);
|
||||
return;
|
||||
}
|
||||
const encryptor = this._roomEncryptors[roomId];
|
||||
const device = this._deviceList.getStoredDevice(userId, deviceId);
|
||||
if (!device) {
|
||||
logger.debug(`Ignoring keyshare for unknown device ${userId}:${deviceId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await encryptor.reshareKeyWithDevice(
|
||||
body.sender_key, body.session_id, userId, device,
|
||||
);
|
||||
} catch (e) {
|
||||
logger.warn(
|
||||
"Failed to re-share keys for session " + body.session_id +
|
||||
" with device " + userId + ":" + device.deviceId, e,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -116,14 +116,17 @@ module.exports.encryptMessageForDevice = async function(
|
||||
* @param {module:base-apis~MatrixBaseApis} baseApis
|
||||
*
|
||||
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
||||
* map from userid to list of devices
|
||||
* map from userid to list of devices to ensure sessions for
|
||||
*
|
||||
* @param {bolean} force If true, establish a new session even if one already exists.
|
||||
* Optional.
|
||||
*
|
||||
* @return {module:client.Promise} resolves once the sessions are complete, to
|
||||
* an Object mapping from userId to deviceId to
|
||||
* {@link module:crypto~OlmSessionResult}
|
||||
*/
|
||||
module.exports.ensureOlmSessionsForDevices = async function(
|
||||
olmDevice, baseApis, devicesByUser,
|
||||
olmDevice, baseApis, devicesByUser, force,
|
||||
) {
|
||||
const devicesWithoutSession = [
|
||||
// [userId, deviceId], ...
|
||||
@@ -141,7 +144,7 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
||||
const deviceId = deviceInfo.deviceId;
|
||||
const key = deviceInfo.getIdentityKey();
|
||||
const sessionId = await olmDevice.getSessionIdForDevice(key);
|
||||
if (sessionId === null) {
|
||||
if (sessionId === null || force) {
|
||||
devicesWithoutSession.push([userId, deviceId]);
|
||||
}
|
||||
result[userId][deviceId] = {
|
||||
@@ -177,7 +180,7 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
||||
for (let j = 0; j < devices.length; j++) {
|
||||
const deviceInfo = devices[j];
|
||||
const deviceId = deviceInfo.deviceId;
|
||||
if (result[userId][deviceId].sessionId) {
|
||||
if (result[userId][deviceId].sessionId && !force) {
|
||||
// we already have a result for this device
|
||||
continue;
|
||||
}
|
||||
|
@@ -206,6 +206,42 @@ export class Backend {
|
||||
return promiseifyTxn(txn).then(() => result);
|
||||
}
|
||||
|
||||
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
|
||||
let stateIndex = 0;
|
||||
const results = [];
|
||||
|
||||
function onsuccess(ev) {
|
||||
const cursor = ev.target.result;
|
||||
if (cursor) {
|
||||
const keyReq = cursor.value;
|
||||
if (keyReq.recipients.includes({userId, deviceId})) {
|
||||
results.push(keyReq);
|
||||
}
|
||||
cursor.continue();
|
||||
} else {
|
||||
// try the next state in the list
|
||||
stateIndex++;
|
||||
if (stateIndex >= wantedStates.length) {
|
||||
// no matches
|
||||
return;
|
||||
}
|
||||
|
||||
const wantedState = wantedStates[stateIndex];
|
||||
const cursorReq = ev.target.source.openCursor(wantedState);
|
||||
cursorReq.onsuccess = onsuccess;
|
||||
}
|
||||
}
|
||||
|
||||
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
|
||||
const store = txn.objectStore("outgoingRoomKeyRequests");
|
||||
|
||||
const wantedState = wantedStates[stateIndex];
|
||||
const cursorReq = store.index("state").openCursor(wantedState);
|
||||
cursorReq.onsuccess = onsuccess;
|
||||
|
||||
return promiseifyTxn(txn).then(() => results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for an existing room key request by id and state, and update it if
|
||||
* found
|
||||
|
@@ -207,6 +207,24 @@ export default class IndexedDBCryptoStore {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for room key requests by target device and state
|
||||
*
|
||||
* @param {string} userId Target user ID
|
||||
* @param {string} deviceId Target device ID
|
||||
* @param {Array<Number>} wantedStates list of acceptable states
|
||||
*
|
||||
* @return {Promise} resolves to a list of all the
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
|
||||
*/
|
||||
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
|
||||
return this._connect().then((backend) => {
|
||||
return backend.getOutgoingRoomKeyRequestsByTarget(
|
||||
userId, deviceId, wantedStates,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for an existing room key request by id and state, and update it if
|
||||
* found
|
||||
|
@@ -145,6 +145,19 @@ export default class MemoryCryptoStore {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
|
||||
const results = [];
|
||||
|
||||
for (const req of this._outgoingRoomKeyRequests) {
|
||||
for (const state of wantedStates) {
|
||||
if (req.state === state && req.recipients.includes({userId, deviceId})) {
|
||||
results.push(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.resolve(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for an existing room key request by id and state, and update it if
|
||||
* found
|
||||
|
Reference in New Issue
Block a user