1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-31 15:24:23 +03:00

Clean up code for handling decryption failures (#4126)

Various improvements, including:

* Defining an enum for decryption failure reasons
* Exposing the reason code as a property on Event
This commit is contained in:
Richard van der Hoff
2024-03-22 17:15:27 +00:00
committed by GitHub
parent a573727662
commit d1259b241c
14 changed files with 282 additions and 147 deletions

View File

@ -80,12 +80,12 @@ import { SecretStorageKeyDescription } from "../../../src/secret-storage";
import {
CrossSigningKey,
CryptoCallbacks,
DecryptionFailureCode,
EventShieldColour,
EventShieldReason,
KeyBackupInfo,
} from "../../../src/crypto-api";
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
import { DecryptionError } from "../../../src/crypto/algorithms";
import { IKeyBackup } from "../../../src/crypto/backup";
import {
createOlmAccount,
@ -470,9 +470,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
await startClientAndAwaitFirstSync();
const awaitUISI = new Promise<void>((resolve) => {
aliceClient.on(MatrixEventEvent.Decrypted, (ev, err) => {
const error = err as DecryptionError;
if (error.code == "MEGOLM_UNKNOWN_INBOUND_SESSION_ID") {
aliceClient.on(MatrixEventEvent.Decrypted, (ev) => {
if (ev.decryptionFailureReason === DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID) {
resolve();
}
});
@ -499,9 +498,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
await startClientAndAwaitFirstSync();
const awaitUnknownIndex = new Promise<void>((resolve) => {
aliceClient.on(MatrixEventEvent.Decrypted, (ev, err) => {
const error = err as DecryptionError;
if (error.code == "OLM_UNKNOWN_MESSAGE_INDEX") {
aliceClient.on(MatrixEventEvent.Decrypted, (ev) => {
if (ev.decryptionFailureReason === DecryptionFailureCode.OLM_UNKNOWN_MESSAGE_INDEX) {
resolve();
}
});
@ -532,13 +530,12 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
await aliceClient.getCrypto()!.importRoomKeys([testData.MEGOLM_SESSION_DATA]);
const awaitDecryptionError = new Promise<void>((resolve) => {
aliceClient.on(MatrixEventEvent.Decrypted, (ev, err) => {
const error = err as DecryptionError;
aliceClient.on(MatrixEventEvent.Decrypted, (ev) => {
// rust and libolm can't have an exact 1:1 mapping for all errors,
// but some errors are part of API and should match
if (
error.code != "MEGOLM_UNKNOWN_INBOUND_SESSION_ID" &&
error.code != "OLM_UNKNOWN_MESSAGE_INDEX"
ev.decryptionFailureReason !== DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID &&
ev.decryptionFailureReason !== DecryptionFailureCode.OLM_UNKNOWN_MESSAGE_INDEX
) {
resolve();
}

View File

@ -27,6 +27,8 @@ import {
THREAD_RELATION_TYPE,
TweakName,
} from "../../../src";
import { DecryptionFailureCode } from "../../../src/crypto-api";
import { DecryptionError } from "../../../src/common-crypto/CryptoBackend";
describe("MatrixEvent", () => {
it("should create copies of itself", () => {
@ -360,20 +362,50 @@ describe("MatrixEvent", () => {
});
});
it("should report decryption errors", async () => {
it("should report unknown decryption errors", async () => {
const decryptionListener = jest.fn();
encryptedEvent.addListener(MatrixEventEvent.Decrypted, decryptionListener);
const testError = new Error("test error");
const crypto = {
decryptEvent: jest.fn().mockRejectedValue(new Error("test error")),
decryptEvent: jest.fn().mockRejectedValue(testError),
} as unknown as Crypto;
await encryptedEvent.attemptDecryption(crypto);
expect(encryptedEvent.isEncrypted()).toBeTruthy();
expect(encryptedEvent.isBeingDecrypted()).toBeFalsy();
expect(encryptedEvent.isDecryptionFailure()).toBeTruthy();
expect(encryptedEvent.decryptionFailureReason).toEqual(DecryptionFailureCode.UNKNOWN_ERROR);
expect(encryptedEvent.isEncryptedDisabledForUnverifiedDevices).toBeFalsy();
expect(encryptedEvent.getContent()).toEqual({
msgtype: "m.bad.encrypted",
body: "** Unable to decrypt: Error: test error **",
});
expect(decryptionListener).toHaveBeenCalledWith(encryptedEvent, testError);
});
it("should report known decryption errors", async () => {
const decryptionListener = jest.fn();
encryptedEvent.addListener(MatrixEventEvent.Decrypted, decryptionListener);
const testError = new DecryptionError(DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID, "uisi");
const crypto = {
decryptEvent: jest.fn().mockRejectedValue(testError),
} as unknown as Crypto;
await encryptedEvent.attemptDecryption(crypto);
expect(encryptedEvent.isEncrypted()).toBeTruthy();
expect(encryptedEvent.isBeingDecrypted()).toBeFalsy();
expect(encryptedEvent.isDecryptionFailure()).toBeTruthy();
expect(encryptedEvent.decryptionFailureReason).toEqual(
DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID,
);
expect(encryptedEvent.isEncryptedDisabledForUnverifiedDevices).toBeFalsy();
expect(encryptedEvent.getContent()).toEqual({
msgtype: "m.bad.encrypted",
body: "** Unable to decrypt: DecryptionError: uisi **",
});
expect(decryptionListener).toHaveBeenCalledWith(encryptedEvent, testError);
});
it(`should report "DecryptionError: The sender has disabled encrypting to unverified devices."`, async () => {
@ -423,6 +455,8 @@ describe("MatrixEvent", () => {
expect(eventAttemptDecryptionSpy).toHaveBeenCalledTimes(2);
expect(crypto.decryptEvent).toHaveBeenCalledTimes(2);
expect(encryptedEvent.getType()).toEqual("m.room.message");
expect(encryptedEvent.isDecryptionFailure()).toBe(false);
expect(encryptedEvent.decryptionFailureReason).toBe(null);
});
});

View File

@ -25,10 +25,11 @@ import { EventType, RelationType, UNSTABLE_MSC2716_MARKER } from "../../src/@typ
import { MatrixEvent, MatrixEventEvent } from "../../src/models/event";
import { M_BEACON } from "../../src/@types/beacon";
import { MatrixClient } from "../../src/client";
import { DecryptionError } from "../../src/crypto/algorithms";
import { defer } from "../../src/utils";
import { Room } from "../../src/models/room";
import { KnownMembership } from "../../src/@types/membership";
import { DecryptionFailureCode } from "../../src/crypto-api";
import { DecryptionError } from "../../src/common-crypto/CryptoBackend";
describe("RoomState", function () {
const roomId = "!foo:bar";
@ -1040,7 +1041,9 @@ describe("RoomState", function () {
content: beacon1RelationContent,
});
jest.spyOn(failedDecryptionRelatedEvent, "isDecryptionFailure").mockReturnValue(true);
mockClient.decryptEventIfNeeded.mockRejectedValue(new DecryptionError("ERR", "msg"));
mockClient.decryptEventIfNeeded.mockRejectedValue(
new DecryptionError(DecryptionFailureCode.UNKNOWN_ERROR, "msg"),
);
// spy on event.once
const eventOnceSpy = jest.spyOn(failedDecryptionRelatedEvent, "once");

View File

@ -16,6 +16,7 @@ limitations under the License.
import { mkDecryptionFailureMatrixEvent, mkEncryptedMatrixEvent, mkMatrixEvent } from "../../src/testing";
import { EventType } from "../../src";
import { DecryptionFailureCode } from "../../src/crypto-api";
describe("testing", () => {
describe("mkMatrixEvent", () => {
@ -60,6 +61,7 @@ describe("testing", () => {
expect(event.sender?.userId).toEqual("@alice:test");
expect(event.isEncrypted()).toBe(true);
expect(event.isDecryptionFailure()).toBe(false);
expect(event.decryptionFailureReason).toBe(null);
expect(event.getContent()).toEqual({ body: "blah" });
expect(event.getType()).toEqual("m.room.message");
});
@ -70,13 +72,14 @@ describe("testing", () => {
const event = await mkDecryptionFailureMatrixEvent({
sender: "@alice:test",
roomId: "!test:room",
code: "UNKNOWN",
code: DecryptionFailureCode.UNKNOWN_ERROR,
msg: "blah",
});
expect(event.sender?.userId).toEqual("@alice:test");
expect(event.isEncrypted()).toBe(true);
expect(event.isDecryptionFailure()).toBe(true);
expect(event.decryptionFailureReason).toEqual(DecryptionFailureCode.UNKNOWN_ERROR);
expect(event.getContent()).toEqual({
body: "** Unable to decrypt: DecryptionError: blah **",
msgtype: "m.bad.encrypted",