1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-28 05:03:59 +03:00

fix indexedDB storage and retry decryption when we get an olm error

This commit is contained in:
Hubert Chathi
2020-01-14 23:47:05 -05:00
parent b3a16cb852
commit f19013143a
5 changed files with 132 additions and 20 deletions

View File

@@ -159,6 +159,16 @@ class DecryptionAlgorithm {
shareKeysWithDevice(keyRequest) { shareKeysWithDevice(keyRequest) {
throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm"); throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm");
} }
/**
* Retry decrypting all the events from a sender that haven't been
* decrypted yet.
*
* @param {string} senderKey the sender's key
*/
async retryDecryptionFromSender(senderKey) {
// ignore by default
}
} }
export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272 export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272

View File

@@ -997,15 +997,17 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
// scheduled, so we needn't send out the request here.) // scheduled, so we needn't send out the request here.)
this._requestKeysForEvent(event); this._requestKeysForEvent(event);
// See if there was a problem with the olm session at the time the
// event was sent. Use a fuzz factor of 2 minutes.
const problem = await this._olmDevice.sessionMayHaveProblems( const problem = await this._olmDevice.sessionMayHaveProblems(
content.sender_key, event.getTs(), content.sender_key, event.getTs() - 120000,
); );
if (problem) { if (problem) {
let problemDescription = PROBLEM_DESCRIPTIONS[problem.type] let problemDescription = PROBLEM_DESCRIPTIONS[problem.type]
|| PROBLEM_DESCRIPTIONS.unknown; || PROBLEM_DESCRIPTIONS.unknown;
if (problem.fixed) { if (problem.fixed) {
problemDescription += problemDescription +=
" Trying to create a new secure channel and re-requesting the keys"; " Trying to create a new secure channel and re-requesting the keys.";
} }
throw new base.DecryptionError( throw new base.DecryptionError(
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID", "MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
@@ -1071,11 +1073,16 @@ MegolmDecryption.prototype._requestKeysForEvent = function(event) {
*/ */
MegolmDecryption.prototype._addEventToPendingList = function(event) { MegolmDecryption.prototype._addEventToPendingList = function(event) {
const content = event.getWireContent(); const content = event.getWireContent();
const k = content.sender_key + "|" + content.session_id; const senderKey = content.sender_key;
if (!this._pendingEvents[k]) { const sessionId = content.session_id;
this._pendingEvents[k] = new Set(); if (!this._pendingEvents[senderKey]) {
this._pendingEvents[senderKey] = new Map();
} }
this._pendingEvents[k].add(event); const senderPendingEvents = this._pendingEvents[senderKey];
if (!senderPendingEvents.has(sessionId)) {
senderPendingEvents.set(sessionId, new Set());
}
senderPendingEvents.get(sessionId).add(event);
}; };
/** /**
@@ -1087,14 +1094,20 @@ MegolmDecryption.prototype._addEventToPendingList = function(event) {
*/ */
MegolmDecryption.prototype._removeEventFromPendingList = function(event) { MegolmDecryption.prototype._removeEventFromPendingList = function(event) {
const content = event.getWireContent(); const content = event.getWireContent();
const k = content.sender_key + "|" + content.session_id; const senderKey = content.sender_key;
if (!this._pendingEvents[k]) { const sessionId = content.session_id;
const senderPendingEvents = this._pendingEvents[senderKey];
const pendingEvents = senderPendingEvents && senderPendingEvents.get(sessionId);
if (!pendingEvents) {
return; return;
} }
this._pendingEvents[k].delete(event); pendingEvents.delete(event);
if (this._pendingEvents[k].size === 0) { if (pendingEvents.size === 0) {
delete this._pendingEvents[k]; senderPendingEvents.delete(senderKey);
}
if (senderPendingEvents.size === 0) {
delete this._pendingEvents[senderKey];
} }
}; };
@@ -1211,10 +1224,17 @@ MegolmDecryption.prototype.onRoomKeyWithheldEvent = async function(event) {
const sender = event.getSender(); const sender = event.getSender();
// if the sender says that they haven't been able to establish an olm // if the sender says that they haven't been able to establish an olm
// session, let's proactively establish one // session, let's proactively establish one
// Note: after we record that the olm session has had a problem, we
// trigger retrying decryption for all the messages from the sender's
// key, so that we can update the error message to indicate the olm
// session problem.
if (await this._olmDevice.getSessionIdForDevice(senderKey)) { if (await this._olmDevice.getSessionIdForDevice(senderKey)) {
// a session has already been established, so we don't need to // a session has already been established, so we don't need to
// create a new one. // create a new one.
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true); await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true);
this.retryDecryptionFromSender(senderKey);
return; return;
} }
const device = this._crypto._deviceList.getDeviceByIdentityKey( const device = this._crypto._deviceList.getDeviceByIdentityKey(
@@ -1226,6 +1246,7 @@ MegolmDecryption.prototype.onRoomKeyWithheldEvent = async function(event) {
": not establishing session", ": not establishing session",
); );
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", false); await this._olmDevice.recordSessionProblem(senderKey, "no_olm", false);
this.retryDecryptionFromSender(senderKey);
return; return;
} }
await olmlib.ensureOlmSessionsForDevices( await olmlib.ensureOlmSessionsForDevices(
@@ -1247,6 +1268,7 @@ MegolmDecryption.prototype.onRoomKeyWithheldEvent = async function(event) {
); );
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true); await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true);
this.retryDecryptionFromSender(senderKey);
await this._baseApis.sendToDevice("m.room.encrypted", { await this._baseApis.sendToDevice("m.room.encrypted", {
[sender]: { [sender]: {
@@ -1404,13 +1426,20 @@ MegolmDecryption.prototype.importRoomKey = function(session) {
* @return {Boolean} whether all messages were successfully decrypted * @return {Boolean} whether all messages were successfully decrypted
*/ */
MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionId) { MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionId) {
const k = senderKey + "|" + sessionId; const senderPendingEvents = this._pendingEvents[senderKey];
const pending = this._pendingEvents[k]; if (!senderPendingEvents) {
return true;
}
const pending = senderPendingEvents.get(sessionId);
if (!pending) { if (!pending) {
return true; return true;
} }
delete this._pendingEvents[k]; pending.delete(sessionId);
if (pending.size === 0) {
this._pendingEvents[senderKey];
}
await Promise.all([...pending].map(async (ev) => { await Promise.all([...pending].map(async (ev) => {
try { try {
@@ -1420,7 +1449,32 @@ MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionI
} }
})); }));
return !this._pendingEvents[k]; // ev.attemptDecryption will re-add to this._pendingEvents if an event
// couldn't be decrypted
return !((this._pendingEvents[senderKey] || {})[sessionId]);
};
MegolmDecryption.prototype.retryDecryptionFromSender = async function(senderKey) {
const senderPendingEvents = this._pendingEvents[senderKey];
logger.warn(senderPendingEvents);
if (!senderPendingEvents) {
return true;
}
delete this._pendingEvents[senderKey];
await Promise.all([...senderPendingEvents].map(async ([_sessionId, pending]) => {
await Promise.all([...pending].map(async (ev) => {
try {
logger.warn(ev.getId());
await ev.attemptDecryption(this._crypto);
} catch (e) {
// don't die if something goes wrong
}
}));
}));
return !this._pendingEvents[senderKey];
}; };
base.registerAlgorithm( base.registerAlgorithm(

View File

@@ -2464,10 +2464,25 @@ Crypto.prototype._onRoomKeyWithheldEvent = function(event) {
return; return;
} }
logger.info(
`Got room key withheld event from ${event.getSender()} (${content.sender_key}) `
+ `for ${content.algorithm}/${content.room_id}/${content.session_id} `
+ `with reason ${content.code} (${content.reason})`,
);
const alg = this._getRoomDecryptor(content.room_id, content.algorithm); const alg = this._getRoomDecryptor(content.room_id, content.algorithm);
if (alg.onRoomKeyWithheldEvent) { if (alg.onRoomKeyWithheldEvent) {
alg.onRoomKeyWithheldEvent(event); alg.onRoomKeyWithheldEvent(event);
} }
if (!content.room_id) {
// retry decryption for all events sent by the sender_key. This will
// update the events to show a message indicating that the olm session was
// wedged.
const roomDecryptors = this._getRoomDecryptors(content.algorithm);
for (const decryptor of roomDecryptors) {
decryptor.retryDecryptionFromSender(content.sender_key);
}
}
}; };
/** /**
@@ -2583,6 +2598,16 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
const algorithm = content.algorithm; const algorithm = content.algorithm;
const deviceKey = content.sender_key; const deviceKey = content.sender_key;
// retry decryption for all events sent by the sender_key. This will
// update the events to show a message indicating that the olm session was
// wedged.
const retryDecryption = () => {
const roomDecryptors = this._getRoomDecryptors(olmlib.MEGOLM_ALGORITHM);
for (const decryptor of roomDecryptors) {
decryptor.retryDecryptionFromSender(deviceKey);
}
};
if (sender === undefined || deviceKey === undefined || deviceKey === undefined) { if (sender === undefined || deviceKey === undefined || deviceKey === undefined) {
return; return;
} }
@@ -2596,6 +2621,8 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
"New session already forced with device " + sender + ":" + deviceKey + "New session already forced with device " + sender + ":" + deviceKey +
" at " + lastNewSessionForced + ": not forcing another", " at " + lastNewSessionForced + ": not forcing another",
); );
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", true);
retryDecryption();
return; return;
} }
@@ -2609,9 +2636,8 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
"Couldn't find device for identity key " + deviceKey + "Couldn't find device for identity key " + deviceKey +
": not re-establishing session", ": not re-establishing session",
); );
await this._olmDevice.recordSessionProblem( await this._olmDevice.recordSessionProblem(deviceKey, "wedged", false);
deviceKey, "wedged", false, retryDecryption();
);
return; return;
} }
const devicesByUser = {}; const devicesByUser = {};
@@ -2644,6 +2670,7 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
); );
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", true); await this._olmDevice.recordSessionProblem(deviceKey, "wedged", true);
retryDecryption();
await this._baseApis.sendToDevice("m.room.encrypted", { await this._baseApis.sendToDevice("m.room.encrypted", {
[sender]: { [sender]: {
@@ -2926,6 +2953,24 @@ Crypto.prototype._getRoomDecryptor = function(roomId, algorithm) {
}; };
/**
* Get all the room decryptors for a given encryption algorithm.
*
* @param {string} algorithm The encryption algorithm
*
* @return {array} An array of room decryptors
*/
Crypto.prototype._getRoomDecryptors = function(algorithm) {
const decryptors = [];
for (const d of Object.values(this._roomDecryptors)) {
if (algorithm in d) {
decryptors.push(d[algorithm]);
}
}
return decryptors;
};
/** /**
* sign the given object with our ed25519 key * sign the given object with our ed25519 key
* *

View File

@@ -450,6 +450,9 @@ export class Backend {
result = null; result = null;
return; return;
} }
problems.sort((a, b) => {
return a.time - b.time;
});
const lastProblem = problems[problems.length - 1]; const lastProblem = problems[problems.length - 1];
for (const problem of problems) { for (const problem of problems) {
if (problem.time > timestamp) { if (problem.time > timestamp) {
@@ -768,7 +771,7 @@ export function upgradeDatabase(db, oldVersion) {
const problemsStore = db.createObjectStore("session_problems", { const problemsStore = db.createObjectStore("session_problems", {
keyPath: ["deviceKey", "time"], keyPath: ["deviceKey", "time"],
}); });
problemsStore.createIndex("deviceKey"); problemsStore.createIndex("deviceKey", "deviceKey");
db.createObjectStore("notified_error_devices", { db.createObjectStore("notified_error_devices", {
keyPath: ["userId", "deviceId"], keyPath: ["userId", "deviceId"],

View File

@@ -417,7 +417,7 @@ export default class IndexedDBCryptoStore {
getEndToEndSessionProblem(deviceKey, timestamp) { getEndToEndSessionProblem(deviceKey, timestamp) {
return this._backendPromise.then(async (backend) => { return this._backendPromise.then(async (backend) => {
await backend.getEndToEndSessionProblem(deviceKey, timestamp); return await backend.getEndToEndSessionProblem(deviceKey, timestamp);
}); });
} }