Skip to content
This repository has been archived by the owner on Oct 22, 2024. It is now read-only.

Commit

Permalink
display invisible crypto decryption errors
Browse files Browse the repository at this point in the history
  • Loading branch information
uhoreg authored and richvdh committed Sep 26, 2024
1 parent ebcfbdd commit 076f097
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 2 deletions.
60 changes: 60 additions & 0 deletions playwright/e2e/crypto/invisible-crypto.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import { expect, test } from "../../element-web-test";
import { autoJoin, createSharedRoomWithUser, verify } from "./utils";
import { Bot } from "../../pages/bot";

test.describe("Invisible cryptography", () => {
test.use({
displayName: "Alice",
botCreateOpts: { displayName: "Bob", autoAcceptInvites: true },
labsFlags: ["feature_invisible_crypto"],
});

test("Messages fail to decrypt when sender is previously verified", async ({
page,
bot: bob,
user: aliceCredentials,
app,
homeserver,
}) => {
await app.client.bootstrapCrossSigning(aliceCredentials);
await autoJoin(bob);

// create an encrypted room
const testRoomId = await createSharedRoomWithUser(app, bob.credentials.userId, {
name: "TestRoom",
initial_state: [
{
type: "m.room.encryption",
state_key: "",
content: {
algorithm: "m.megolm.v1.aes-sha2",
},
},
],
});

// Verify Bob
await verify(app, bob);

// Bob logs in a new device and resets cross-signing
const bobSecondDevice = new Bot(page, homeserver, {
bootstrapSecretStorage: true,
bootstrapCrossSigning: true,
setupNewCrossSigning: true,
});
bobSecondDevice.setCredentials(await homeserver.loginUser(bob.credentials.userId, bob.credentials.password));
await bobSecondDevice.prepareClient();

/* should show an error for a message from a previously verified device */
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from previously verified");
const lastTile = page.locator(".mx_EventTile_last");
await expect(lastTile).toContainText("Verified identity has changed");
});
});
5 changes: 5 additions & 0 deletions playwright/pages/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export interface CreateBotOpts {
* Whether to generate cross-signing keys
*/
bootstrapCrossSigning?: boolean;
/**
* Whether to reset the cross-signing keys even if keys already exist
*/
setupNewCrossSigning?: boolean;
/**
* Whether to bootstrap the secret storage
*/
Expand Down Expand Up @@ -186,6 +190,7 @@ export class Bot extends Client {
await cli.getCrypto()!.getUserDeviceInfo([credentials.userId]);

await cli.getCrypto()!.bootstrapCrossSigning({
setupNewCrossSigning: opts.setupNewCrossSigning,
authUploadDeviceSigningKeys: async (func) => {
await func({
type: "m.login.password",
Expand Down
16 changes: 16 additions & 0 deletions res/css/views/messages/_DecryptionFailureBody.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,19 @@ Please see LICENSE files in the repository root for full details.
color: $secondary-content;
font-style: italic;
}

.mx_DecryptionFailureVerifiedIdentityChanged > span {
color: $e2e-warning-color;
border-radius: $font-16px;
border-width: 1px;
border-color: $e2e-warning-color;
border-style: solid;
padding: $font-1px 0.4em $font-1px 0.4em;
display: inline-flex;
align-items: center;

.mx_Icon {
margin-inline-start: -0.3em;
margin-inline-end: 0.2em;
}
}
30 changes: 28 additions & 2 deletions src/components/views/messages/DecryptionFailureBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import classNames from "classnames";
import React, { forwardRef, ForwardRefExoticComponent, useContext } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api";

import { _t } from "../../../languageHandler";
import { IBodyProps } from "./IBodyProps";
import { LocalDeviceVerificationStateContext } from "../../../contexts/LocalDeviceVerificationStateContext";
import { Icon as WarningBadgeIcon } from "../../../../res/img/compound/error-16px.svg";

function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined): string {
function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined): string | React.JSX.Element {
switch (mxEvent.decryptionFailureReason) {
case DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE:
return _t("timeline|decryption_failure|blocked");
Expand All @@ -33,15 +35,39 @@ function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined):

case DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED:
return _t("timeline|decryption_failure|historical_event_user_not_joined");

case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
return (
<span>
<WarningBadgeIcon className="mx_Icon mx_Icon_16" />
{_t("timeline|decryption_failure|sender_identity_previously_verified")}
</span>
);

case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE:
// TODO: event should be hidden instead of showing this error (only
// happens when invisible crypto is enabled)
return _t("encryption|event_shield_reason_unsigned_device");
}
return _t("timeline|decryption_failure|unable_to_decrypt");
}

function getErrorExtraClass(mxEvent: MatrixEvent): Record<string, boolean> {
switch (mxEvent.decryptionFailureReason) {
case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
return { mx_DecryptionFailureVerifiedIdentityChanged: true };

default:
return {};
}
}

// A placeholder element for messages that could not be decrypted
export const DecryptionFailureBody = forwardRef<HTMLDivElement, IBodyProps>(({ mxEvent }, ref): React.JSX.Element => {
const verificationState = useContext(LocalDeviceVerificationStateContext);
const classes = classNames("mx_DecryptionFailureBody", "mx_EventTile_content", getErrorExtraClass(mxEvent));
return (
<div className="mx_DecryptionFailureBody mx_EventTile_content" ref={ref}>
<div className={classes} ref={ref}>
{getErrorMessage(mxEvent, verificationState)}
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -3293,6 +3293,7 @@
"historical_event_no_key_backup": "Historical messages are not available on this device",
"historical_event_unverified_device": "You need to verify this device for access to historical messages",
"historical_event_user_not_joined": "You don't have access to this message",
"sender_identity_previously_verified": "Verified identity has changed",
"unable_to_decrypt": "Unable to decrypt message"
},
"disambiguated_profile": "%(displayName)s (%(matrixId)s)",
Expand Down
28 changes: 28 additions & 0 deletions test/components/views/messages/DecryptionFailureBody-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,32 @@ describe("DecryptionFailureBody", () => {
// Then
expect(container).toHaveTextContent("You don't have access to this message");
});

it("should handle messages from users who change identities after verification", async () => {
// When
const event = await mkDecryptionFailureMatrixEvent({
code: DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED,
msg: "User previously verified",
roomId: "fakeroom",
sender: "fakesender",
});
const { container } = customRender(event);

// Then
expect(container).toHaveTextContent("Verified identity has changed");
});

it("should handle messages from unverified devices", async () => {
// When
const event = await mkDecryptionFailureMatrixEvent({
code: DecryptionFailureCode.UNSIGNED_SENDER_DEVICE,
msg: "Unsigned device",
roomId: "fakeroom",
sender: "fakesender",
});
const { container } = customRender(event);

// Then
expect(container).toHaveTextContent("Encrypted by a device not verified by its owner");
});
});

0 comments on commit 076f097

Please sign in to comment.