You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-25 05:23:13 +03:00
Refactoring and simplification in decryption error handling (#4138)
* Clean up decryption failure integ tests * Fix the names * Stop waiting as soon as the event is decrypted, even if code is wrong (so tests fail rather than time out if the code is wrong) * Bump timeouts on some tests These tend to fail due to slow init of wasm artifacts * Factor out `onDecryptionKeyMissingError` call * Factor out `onMegolmDecryptionError`
This commit is contained in:
committed by
GitHub
parent
cfcd191cbf
commit
dbab185f9d
@@ -81,7 +81,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(
|
||||||
|
async () => {
|
||||||
// anything that we don't have a specific matcher for silently returns a 404
|
// anything that we don't have a specific matcher for silently returns a 404
|
||||||
fetchMock.catch(404);
|
fetchMock.catch(404);
|
||||||
fetchMock.config.warnOnFallback = false;
|
fetchMock.config.warnOnFallback = false;
|
||||||
@@ -107,7 +108,10 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s
|
|||||||
});
|
});
|
||||||
|
|
||||||
await initCrypto(aliceClient);
|
await initCrypto(aliceClient);
|
||||||
});
|
},
|
||||||
|
/* it can take a while to initialise the crypto library on the first pass, so bump up the timeout. */
|
||||||
|
10000,
|
||||||
|
);
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await aliceClient.stopClient();
|
await aliceClient.stopClient();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import * as testUtils from "../../test-utils/test-utils";
|
|||||||
import {
|
import {
|
||||||
advanceTimersUntil,
|
advanceTimersUntil,
|
||||||
CRYPTO_BACKENDS,
|
CRYPTO_BACKENDS,
|
||||||
|
emitPromise,
|
||||||
getSyncResponse,
|
getSyncResponse,
|
||||||
InitCrypto,
|
InitCrypto,
|
||||||
mkEventCustom,
|
mkEventCustom,
|
||||||
@@ -466,19 +467,13 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Unable to decrypt error codes", function () {
|
describe("Unable to decrypt error codes", function () {
|
||||||
it("Encryption fails with expected UISI error", async () => {
|
it("Decryption fails with UISI error", async () => {
|
||||||
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||||
await startClientAndAwaitFirstSync();
|
await startClientAndAwaitFirstSync();
|
||||||
|
|
||||||
const awaitUISI = new Promise<void>((resolve) => {
|
// A promise which resolves, with the MatrixEvent which wraps the event, once the decryption fails.
|
||||||
aliceClient.on(MatrixEventEvent.Decrypted, (ev) => {
|
const awaitDecryption = emitPromise(aliceClient, MatrixEventEvent.Decrypted);
|
||||||
if (ev.decryptionFailureReason === DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Alice gets both the events in a single sync
|
|
||||||
const syncResponse = {
|
const syncResponse = {
|
||||||
next_batch: 1,
|
next_batch: 1,
|
||||||
rooms: {
|
rooms: {
|
||||||
@@ -490,21 +485,16 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
|
|
||||||
syncResponder.sendOrQueueSyncResponse(syncResponse);
|
syncResponder.sendOrQueueSyncResponse(syncResponse);
|
||||||
await syncPromise(aliceClient);
|
await syncPromise(aliceClient);
|
||||||
|
const ev = await awaitDecryption;
|
||||||
await awaitUISI;
|
expect(ev.decryptionFailureReason).toEqual(DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Encryption fails with expected Unknown Index error", async () => {
|
it("Decryption fails with Unknown Index error", async () => {
|
||||||
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||||
await startClientAndAwaitFirstSync();
|
await startClientAndAwaitFirstSync();
|
||||||
|
|
||||||
const awaitUnknownIndex = new Promise<void>((resolve) => {
|
// A promise which resolves, with the MatrixEvent which wraps the event, once the decryption fails.
|
||||||
aliceClient.on(MatrixEventEvent.Decrypted, (ev) => {
|
const awaitDecryption = emitPromise(aliceClient, MatrixEventEvent.Decrypted);
|
||||||
if (ev.decryptionFailureReason === DecryptionFailureCode.OLM_UNKNOWN_MESSAGE_INDEX) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await aliceClient.getCrypto()!.importRoomKeys([testData.RATCHTED_MEGOLM_SESSION_DATA]);
|
await aliceClient.getCrypto()!.importRoomKeys([testData.RATCHTED_MEGOLM_SESSION_DATA]);
|
||||||
|
|
||||||
@@ -521,10 +511,11 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
syncResponder.sendOrQueueSyncResponse(syncResponse);
|
syncResponder.sendOrQueueSyncResponse(syncResponse);
|
||||||
await syncPromise(aliceClient);
|
await syncPromise(aliceClient);
|
||||||
|
|
||||||
await awaitUnknownIndex;
|
const ev = await awaitDecryption;
|
||||||
|
expect(ev.decryptionFailureReason).toEqual(DecryptionFailureCode.OLM_UNKNOWN_MESSAGE_INDEX);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Encryption fails with Unable to decrypt for other errors", async () => {
|
it("Decryption fails with Unable to decrypt for other errors", async () => {
|
||||||
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||||
await startClientAndAwaitFirstSync();
|
await startClientAndAwaitFirstSync();
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(
|
||||||
|
async () => {
|
||||||
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
||||||
|
|
||||||
// ignore requests to send room key requests
|
// ignore requests to send room key requests
|
||||||
@@ -213,7 +214,9 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
|
|||||||
await waitForDeviceList();
|
await waitForDeviceList();
|
||||||
await aliceClient.getCrypto()!.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
await aliceClient.getCrypto()!.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
||||||
await aliceClient.getCrypto()!.checkKeyBackupAndEnable();
|
await aliceClient.getCrypto()!.checkKeyBackupAndEnable();
|
||||||
});
|
} /* it can take a while to initialise the crypto library on the first pass, so bump up the timeout. */,
|
||||||
|
10000,
|
||||||
|
);
|
||||||
|
|
||||||
it("Alice checks key backups when receiving a message she can't decrypt", async () => {
|
it("Alice checks key backups when receiving a message she can't decrypt", async () => {
|
||||||
fetchMock.get("express:/_matrix/client/v3/room_keys/keys/:room_id/:session_id", (url, request) => {
|
fetchMock.get("express:/_matrix/client/v3/room_keys/keys/:room_id/:session_id", (url, request) => {
|
||||||
|
|||||||
@@ -1683,52 +1683,50 @@ class EventDecryptor {
|
|||||||
forwardingCurve25519KeyChain: res.forwardingCurve25519KeyChain,
|
forwardingCurve25519KeyChain: res.forwardingCurve25519KeyChain,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// We need to map back to regular decryption errors (used for analytics for example)
|
|
||||||
// The DecryptionErrors are used by react-sdk so is implicitly part of API, but poorly typed
|
|
||||||
if (err instanceof RustSdkCryptoJs.MegolmDecryptionError) {
|
if (err instanceof RustSdkCryptoJs.MegolmDecryptionError) {
|
||||||
|
this.onMegolmDecryptionError(event, err);
|
||||||
|
} else {
|
||||||
|
throw new DecryptionError(DecryptionFailureCode.UNKNOWN_ERROR, "Unknown error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a `MegolmDecryptionError` returned by the rust SDK.
|
||||||
|
*
|
||||||
|
* Fires off a request to the `perSessionBackupDownloader`, if appropriate, and then throws a `DecryptionError`.
|
||||||
|
*/
|
||||||
|
private onMegolmDecryptionError(event: MatrixEvent, err: RustSdkCryptoJs.MegolmDecryptionError): never {
|
||||||
const content = event.getWireContent();
|
const content = event.getWireContent();
|
||||||
let jsError;
|
|
||||||
|
// If the error looks like it might be recoverable from backup, queue up a request to try that.
|
||||||
|
if (
|
||||||
|
err.code === RustSdkCryptoJs.DecryptionErrorCode.MissingRoomKey ||
|
||||||
|
err.code === RustSdkCryptoJs.DecryptionErrorCode.UnknownMessageIndex
|
||||||
|
) {
|
||||||
|
this.perSessionBackupDownloader.onDecryptionKeyMissingError(event.getRoomId()!, content.session_id!);
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorDetails = { session: content.sender_key + "|" + content.session_id };
|
||||||
switch (err.code) {
|
switch (err.code) {
|
||||||
case RustSdkCryptoJs.DecryptionErrorCode.MissingRoomKey: {
|
case RustSdkCryptoJs.DecryptionErrorCode.MissingRoomKey:
|
||||||
jsError = new DecryptionError(
|
throw new DecryptionError(
|
||||||
DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID,
|
DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID,
|
||||||
"The sender's device has not sent us the keys for this message.",
|
"The sender's device has not sent us the keys for this message.",
|
||||||
{
|
errorDetails,
|
||||||
session: content.sender_key + "|" + content.session_id,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
this.perSessionBackupDownloader.onDecryptionKeyMissingError(
|
|
||||||
event.getRoomId()!,
|
case RustSdkCryptoJs.DecryptionErrorCode.UnknownMessageIndex:
|
||||||
event.getWireContent().session_id!,
|
throw new DecryptionError(
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RustSdkCryptoJs.DecryptionErrorCode.UnknownMessageIndex: {
|
|
||||||
jsError = new DecryptionError(
|
|
||||||
DecryptionFailureCode.OLM_UNKNOWN_MESSAGE_INDEX,
|
DecryptionFailureCode.OLM_UNKNOWN_MESSAGE_INDEX,
|
||||||
"The sender's device has not sent us the keys for this message at this index.",
|
"The sender's device has not sent us the keys for this message at this index.",
|
||||||
{
|
errorDetails,
|
||||||
session: content.sender_key + "|" + content.session_id,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
this.perSessionBackupDownloader.onDecryptionKeyMissingError(
|
|
||||||
event.getRoomId()!,
|
|
||||||
event.getWireContent().session_id!,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// We don't map MismatchedIdentityKeys for now, as there is no equivalent in legacy.
|
// We don't map MismatchedIdentityKeys for now, as there is no equivalent in legacy.
|
||||||
// Just put it on the `UNKNOWN_ERROR` bucket.
|
// Just put it on the `UNKNOWN_ERROR` bucket.
|
||||||
default: {
|
default:
|
||||||
jsError = new DecryptionError(DecryptionFailureCode.UNKNOWN_ERROR, err.description, {
|
throw new DecryptionError(DecryptionFailureCode.UNKNOWN_ERROR, err.description, errorDetails);
|
||||||
session: content.sender_key + "|" + content.session_id,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw jsError;
|
|
||||||
}
|
|
||||||
throw new DecryptionError(DecryptionFailureCode.UNKNOWN_ERROR, "Unknown error");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user