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

Commit 034cedc

Browse files
committed
add invisible decryption config flag and show invisible crypto decryption errors
1 parent 75918f5 commit 034cedc

File tree

8 files changed

+137
-1
lines changed

8 files changed

+137
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
Copyright 2024 New Vector Ltd.
3+
4+
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
5+
Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { expect, test } from "../../element-web-test";
9+
import { autoJoin, createSharedRoomWithUser, verify } from "./utils";
10+
import { Bot } from "../../pages/bot";
11+
12+
test.describe("Invisible cryptography", () => {
13+
test.use({
14+
displayName: "Alice",
15+
botCreateOpts: { displayName: "Bob", autoAcceptInvites: true },
16+
labsFlags: ["feature_invisible_crypto"],
17+
});
18+
19+
test("Messages fail to decrypt when sender is previously verified", async ({
20+
page,
21+
bot: bob,
22+
user: aliceCredentials,
23+
app,
24+
homeserver,
25+
}) => {
26+
await app.client.bootstrapCrossSigning(aliceCredentials);
27+
await autoJoin(bob);
28+
29+
// create an encrypted room
30+
const testRoomId = await createSharedRoomWithUser(app, bob.credentials.userId, {
31+
name: "TestRoom",
32+
initial_state: [
33+
{
34+
type: "m.room.encryption",
35+
state_key: "",
36+
content: {
37+
algorithm: "m.megolm.v1.aes-sha2",
38+
},
39+
},
40+
],
41+
});
42+
43+
// Verify Bob
44+
await verify(app, bob);
45+
46+
// Bob logs in a new device and resets cross-signing
47+
const bobSecondDevice = new Bot(page, homeserver, {
48+
bootstrapSecretStorage: true,
49+
bootstrapCrossSigning: true,
50+
setupNewCrossSigning: true,
51+
});
52+
bobSecondDevice.setCredentials(await homeserver.loginUser(bob.credentials.userId, bob.credentials.password));
53+
await bobSecondDevice.prepareClient();
54+
55+
/* should show an error for a message from a previously verified device */
56+
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from previously verified");
57+
const lastTile = page.locator(".mx_EventTile_last");
58+
await expect(lastTile).toContainText("Verified identity has changed");
59+
});
60+
});

playwright/pages/bot.ts

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ export interface CreateBotOpts {
3737
* Whether to generate cross-signing keys
3838
*/
3939
bootstrapCrossSigning?: boolean;
40+
/**
41+
* Whether to reset the cross-signing keys even if keys already exist
42+
*/
43+
setupNewCrossSigning?: boolean;
4044
/**
4145
* Whether to bootstrap the secret storage
4246
*/
@@ -186,6 +190,7 @@ export class Bot extends Client {
186190
await cli.getCrypto()!.getUserDeviceInfo([credentials.userId]);
187191

188192
await cli.getCrypto()!.bootstrapCrossSigning({
193+
setupNewCrossSigning: opts.setupNewCrossSigning,
189194
authUploadDeviceSigningKeys: async (func) => {
190195
await func({
191196
type: "m.login.password",

res/css/views/messages/_DecryptionFailureBody.pcss

+4
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ Please see LICENSE files in the repository root for full details.
1010
color: $secondary-content;
1111
font-style: italic;
1212
}
13+
14+
.mx_DecryptionFailureVerifiedIdentityChanged {
15+
color: red;
16+
}

src/MatrixClientPeg.ts

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import { VerificationMethod } from "matrix-js-sdk/src/types";
2525
import * as utils from "matrix-js-sdk/src/utils";
2626
import { logger } from "matrix-js-sdk/src/logger";
27+
import { CryptoMode } from "matrix-js-sdk/src/crypto-api";
2728

2829
import createMatrixClient from "./utils/createMatrixClient";
2930
import SettingsStore from "./settings/SettingsStore";
@@ -343,6 +344,10 @@ class MatrixClientPegClass implements IMatrixClientPeg {
343344
});
344345

345346
StorageManager.setCryptoInitialised(true);
347+
348+
if (SettingsStore.getValue("feature_invisible_crypto")) {
349+
this.matrixClient.getCrypto()!.setCryptoMode(CryptoMode.Invisible);
350+
}
346351
// TODO: device dehydration and whathaveyou
347352
return;
348353
}

src/components/views/messages/DecryptionFailureBody.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,33 @@ function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined):
3333

3434
case DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED:
3535
return _t("timeline|decryption_failure|historical_event_user_not_joined");
36+
37+
case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
38+
return _t("timeline|decryption_failure|sender_identity_previously_verified");
39+
40+
case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE:
41+
// TODO: event should be hidden instead of showing this error (only
42+
// happens when invisible crypto is enabled)
43+
return _t("encryption|event_shield_reason_unsigned_device");
3644
}
3745
return _t("timeline|decryption_failure|unable_to_decrypt");
3846
}
3947

48+
function getErrorExtraClass(mxEvent: MatrixEvent): string {
49+
switch (mxEvent.decryptionFailureReason) {
50+
case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
51+
return " mx_DecryptionFailureVerifiedIdentityChanged";
52+
53+
default:
54+
return "";
55+
}
56+
}
57+
4058
// A placeholder element for messages that could not be decrypted
4159
export const DecryptionFailureBody = forwardRef<HTMLDivElement, IBodyProps>(({ mxEvent }, ref): React.JSX.Element => {
4260
const verificationState = useContext(LocalDeviceVerificationStateContext);
4361
return (
44-
<div className="mx_DecryptionFailureBody mx_EventTile_content" ref={ref}>
62+
<div className={"mx_DecryptionFailureBody mx_EventTile_content" + getErrorExtraClass(mxEvent)} ref={ref}>
4563
{getErrorMessage(mxEvent, verificationState)}
4664
</div>
4765
);

src/i18n/strings/en_EN.json

+3
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,8 @@
14441444
"group_widgets": "Widgets",
14451445
"hidebold": "Hide notification dot (only display counters badges)",
14461446
"html_topic": "Show HTML representation of room topics",
1447+
"invisible_crypto": "Invisible cryptography",
1448+
"invisible_crypto": "Invisible cryptography is an experimental cryptography mode that uses cross-signing to provide a cleaner cryptography experience. If you enable this mode, you may be unable to communicate with users who have not cross-signed their devices.",
14471449
"join_beta": "Join the beta",
14481450
"join_beta_reload": "Joining the beta will reload %(brand)s.",
14491451
"jump_to_date": "Jump to date (adds /jumptodate and jump to date headers)",
@@ -3291,6 +3293,7 @@
32913293
"historical_event_no_key_backup": "Historical messages are not available on this device",
32923294
"historical_event_unverified_device": "You need to verify this device for access to historical messages",
32933295
"historical_event_user_not_joined": "You don't have access to this message",
3296+
"sender_identity_previously_verified": "Verified identity has changed",
32943297
"unable_to_decrypt": "Unable to decrypt message"
32953298
},
32963299
"disambiguated_profile": "%(displayName)s (%(matrixId)s)",

src/settings/Settings.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details.
1010
import React, { ReactNode } from "react";
1111

1212
import { _t, _td, TranslationKey } from "../languageHandler";
13+
import InvisibleCryptoController from "./controllers/InvisibleCryptoController";
1314
import {
1415
NotificationBodyEnabledController,
1516
NotificationsEnabledController,
@@ -316,6 +317,16 @@ export const SETTINGS: { [setting: string]: ISetting } = {
316317
supportedLevelsAreOrdered: true,
317318
default: false,
318319
},
320+
"feature_invisible_crypto": {
321+
isFeature: true,
322+
labsGroup: LabGroup.Encryption,
323+
controller: new InvisibleCryptoController(),
324+
displayName: _td("labs|invisible_crypto"),
325+
description: _td("labs|invisible_crypto_description"),
326+
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
327+
supportedLevelsAreOrdered: true,
328+
default: false,
329+
},
319330
"useOnlyCurrentProfiles": {
320331
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
321332
displayName: _td("settings|disable_historical_profile"),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
Copyright 2024 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { CryptoMode } from "matrix-js-sdk/src/crypto-api";
18+
19+
import SettingController from "./SettingController";
20+
import { MatrixClientPeg } from "../../MatrixClientPeg";
21+
import { SettingLevel } from "../SettingLevel";
22+
23+
export default class InvisibleCryptoController extends SettingController {
24+
public onChange(level: SettingLevel, roomId: string, newValue: any): void {
25+
console.log("Setting crypto mode to ", newValue ? 2 : 0);
26+
MatrixClientPeg.safeGet()
27+
.getCrypto()!
28+
.setCryptoMode(newValue ? CryptoMode.Invisible : CryptoMode.Legacy);
29+
}
30+
}

0 commit comments

Comments
 (0)