1
0
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:
David Baker
2018-11-08 19:09:28 +00:00
parent 2a6a67c6cc
commit d74ed508f9
10 changed files with 476 additions and 17 deletions

View File

@@ -1,9 +1,13 @@
"use strict";
import 'source-map-support/register'; import 'source-map-support/register';
import Crypto from '../../lib/crypto'; import Crypto from '../../lib/crypto';
import expect from 'expect'; 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 sdk = require("../..");
const Olm = global.Olm; const Olm = global.Olm;
@@ -20,4 +24,96 @@ describe("Crypto", function() {
it("Crypto exposes the correct olm library version", function() { it("Crypto exposes the correct olm library version", function() {
expect(Crypto.getOlmVersion()[0]).toEqual(3); 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");
});
});
}); });

View File

@@ -18,6 +18,7 @@ import Crypto from '../../../../lib/crypto';
const MatrixEvent = sdk.MatrixEvent; const MatrixEvent = sdk.MatrixEvent;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; 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'; const ROOM_ID = '!ROOM:ID';
@@ -34,9 +35,11 @@ describe("MegolmDecryption", function() {
let mockCrypto; let mockCrypto;
let mockBaseApis; let mockBaseApis;
beforeEach(function() { beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
await Olm.init();
mockCrypto = testUtils.mock(Crypto, 'Crypto'); mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockBaseApis = {}; mockBaseApis = {};
@@ -66,7 +69,6 @@ describe("MegolmDecryption", function() {
describe('receives some keys:', function() { describe('receives some keys:', function() {
let groupSession; let groupSession;
beforeEach(async function() { beforeEach(async function() {
await Olm.init();
groupSession = new global.Olm.OutboundGroupSession(); groupSession = new global.Olm.OutboundGroupSession();
groupSession.create(); groupSession.create();
@@ -263,5 +265,92 @@ describe("MegolmDecryption", function() {
// test is successful if no exception is thrown // 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);
});
}); });
}); });

View File

@@ -461,6 +461,12 @@ OlmDevice.prototype.createOutboundSession = async function(
session.create_outbound(account, theirIdentityKey, theirOneTimeKey); session.create_outbound(account, theirIdentityKey, theirOneTimeKey);
newSessionId = session.session_id(); newSessionId = session.session_id();
this._storeAccount(txn, account); 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); this._saveSession(theirIdentityKey, session, txn);
} finally { } finally {
session.free(); session.free();
@@ -725,7 +731,7 @@ OlmDevice.prototype._saveOutboundGroupSession = function(session) {
*/ */
OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) { OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) {
const pickled = this._outboundGroupSessionStore[sessionId]; const pickled = this._outboundGroupSessionStore[sessionId];
if (pickled === null) { if (pickled === undefined) {
throw new Error("Unknown outbound group session " + sessionId); 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} roomId room in which the message was received
* @param {string} senderKey base64-encoded curve25519 key of the sender * @param {string} senderKey base64-encoded curve25519 key of the sender
* @param {string} sessionId session identifier * @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, * @returns {Promise<{chain_index: number, key: string,
* forwarding_curve25519_key_chain: Array<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 * details of the session key. The key is a base64-encoded megolm key in
* export format. * 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( OlmDevice.prototype.getInboundGroupSessionKey = async function(
roomId, senderKey, sessionId, roomId, senderKey, sessionId, chainIndex,
) { ) {
let result; let result;
await this._cryptoStore.doTxn( await this._cryptoStore.doTxn(
@@ -1079,14 +1090,19 @@ OlmDevice.prototype.getInboundGroupSessionKey = async function(
result = null; result = null;
return; 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 claimedKeys = sessionData.keysClaimed || {};
const senderEd25519Key = claimedKeys.ed25519 || null; const senderEd25519Key = claimedKeys.ed25519 || null;
result = { result = {
"chain_index": messageIndex, "chain_index": chainIndex,
"key": session.export_session(messageIndex), "key": exportedSession,
"forwarding_curve25519_key_chain": "forwarding_curve25519_key_chain":
sessionData.forwardingCurve25519KeyChain || [], sessionData.forwardingCurve25519KeyChain || [],
"sender_claimed_ed25519_key": senderEd25519Key, "sender_claimed_ed25519_key": senderEd25519Key,

View File

@@ -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 // start the background timer to send queued requests, if the timer isn't
// already running // already running
_startTimer() { _startTimer() {

View File

@@ -144,6 +144,11 @@ function MegolmEncryption(params) {
// room). // room).
this._setupPromise = Promise.resolve(); 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 // default rotation periods
this._sessionRotationPeriodMsgs = 100; this._sessionRotationPeriodMsgs = 100;
this._sessionRotationPeriodMs = 7 * 24 * 3600 * 1000; this._sessionRotationPeriodMs = 7 * 24 * 3600 * 1000;
@@ -195,6 +200,7 @@ MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
if (!session) { if (!session) {
logger.log(`Starting new megolm session for room ${self._roomId}`); logger.log(`Starting new megolm session for room ${self._roomId}`);
session = await self._prepareNewSession(); session = await self._prepareNewSession();
self._outboundSessions[session.sessionId] = session;
} }
// now check if we need to share with any devices // 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 {module:crypto/algorithms/megolm.OutboundSessionInfo} session
* *
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser * @param {object<string, module:crypto/deviceinfo[]>} devicesByUser

View File

@@ -1133,6 +1133,8 @@ Crypto.prototype._onToDeviceEvent = function(event) {
this._onRoomKeyEvent(event); this._onRoomKeyEvent(event);
} else if (event.getType() == "m.room_key_request") { } else if (event.getType() == "m.room_key_request") {
this._onRoomKeyRequestEvent(event); this._onRoomKeyRequestEvent(event);
} else if (event.getContent().msgtype === "m.bad.encrypted") {
this._onToDeviceBadEncrypted(event);
} else if (event.isBeingDecrypted()) { } else if (event.isBeingDecrypted()) {
// once the event has been decrypted, try again // once the event has been decrypted, try again
event.once('Event.decrypted', (ev) => { event.once('Event.decrypted', (ev) => {
@@ -1162,6 +1164,74 @@ Crypto.prototype._onRoomKeyEvent = function(event) {
alg.onRoomKeyEvent(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 * 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})`); ` for ${roomId} / ${body.session_id} (id ${req.requestId})`);
if (userId !== this._userId) { if (userId !== this._userId) {
// TODO: determine if we sent this device the keys already: in if (!this._roomEncryptors[roomId]) {
// which case we can do so again. logger.debug(`room key request for unencrypted room ${roomId}`);
logger.log("Ignoring room key request from other user for now"); 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; return;
} }

View File

@@ -116,14 +116,17 @@ module.exports.encryptMessageForDevice = async function(
* @param {module:base-apis~MatrixBaseApis} baseApis * @param {module:base-apis~MatrixBaseApis} baseApis
* *
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser * @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 * @return {module:client.Promise} resolves once the sessions are complete, to
* an Object mapping from userId to deviceId to * an Object mapping from userId to deviceId to
* {@link module:crypto~OlmSessionResult} * {@link module:crypto~OlmSessionResult}
*/ */
module.exports.ensureOlmSessionsForDevices = async function( module.exports.ensureOlmSessionsForDevices = async function(
olmDevice, baseApis, devicesByUser, olmDevice, baseApis, devicesByUser, force,
) { ) {
const devicesWithoutSession = [ const devicesWithoutSession = [
// [userId, deviceId], ... // [userId, deviceId], ...
@@ -141,7 +144,7 @@ module.exports.ensureOlmSessionsForDevices = async function(
const deviceId = deviceInfo.deviceId; const deviceId = deviceInfo.deviceId;
const key = deviceInfo.getIdentityKey(); const key = deviceInfo.getIdentityKey();
const sessionId = await olmDevice.getSessionIdForDevice(key); const sessionId = await olmDevice.getSessionIdForDevice(key);
if (sessionId === null) { if (sessionId === null || force) {
devicesWithoutSession.push([userId, deviceId]); devicesWithoutSession.push([userId, deviceId]);
} }
result[userId][deviceId] = { result[userId][deviceId] = {
@@ -177,7 +180,7 @@ module.exports.ensureOlmSessionsForDevices = async function(
for (let j = 0; j < devices.length; j++) { for (let j = 0; j < devices.length; j++) {
const deviceInfo = devices[j]; const deviceInfo = devices[j];
const deviceId = deviceInfo.deviceId; const deviceId = deviceInfo.deviceId;
if (result[userId][deviceId].sessionId) { if (result[userId][deviceId].sessionId && !force) {
// we already have a result for this device // we already have a result for this device
continue; continue;
} }

View File

@@ -206,6 +206,42 @@ export class Backend {
return promiseifyTxn(txn).then(() => result); 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 * Look for an existing room key request by id and state, and update it if
* found * found

View File

@@ -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 * Look for an existing room key request by id and state, and update it if
* found * found

View File

@@ -145,6 +145,19 @@ export default class MemoryCryptoStore {
return Promise.resolve(null); 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 * Look for an existing room key request by id and state, and update it if
* found * found