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

Commit

Permalink
add invisible decryption config flag and show invisible crypto decryp…
Browse files Browse the repository at this point in the history
…tion errors
  • Loading branch information
uhoreg committed Sep 18, 2024
1 parent 75918f5 commit 034cedc
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 1 deletion.
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
4 changes: 4 additions & 0 deletions res/css/views/messages/_DecryptionFailureBody.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ Please see LICENSE files in the repository root for full details.
color: $secondary-content;
font-style: italic;
}

.mx_DecryptionFailureVerifiedIdentityChanged {
color: red;
}
5 changes: 5 additions & 0 deletions src/MatrixClientPeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { VerificationMethod } from "matrix-js-sdk/src/types";
import * as utils from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { CryptoMode } from "matrix-js-sdk/src/crypto-api";

import createMatrixClient from "./utils/createMatrixClient";
import SettingsStore from "./settings/SettingsStore";
Expand Down Expand Up @@ -343,6 +344,10 @@ class MatrixClientPegClass implements IMatrixClientPeg {
});

StorageManager.setCryptoInitialised(true);

if (SettingsStore.getValue("feature_invisible_crypto")) {
this.matrixClient.getCrypto()!.setCryptoMode(CryptoMode.Invisible);
}
// TODO: device dehydration and whathaveyou
return;
}
Expand Down
20 changes: 19 additions & 1 deletion src/components/views/messages/DecryptionFailureBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,33 @@ 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 _t("timeline|decryption_failure|sender_identity_previously_verified");

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): string {
switch (mxEvent.decryptionFailureReason) {
case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
return " mx_DecryptionFailureVerifiedIdentityChanged";

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);
return (
<div className="mx_DecryptionFailureBody mx_EventTile_content" ref={ref}>
<div className={"mx_DecryptionFailureBody mx_EventTile_content" + getErrorExtraClass(mxEvent)} ref={ref}>
{getErrorMessage(mxEvent, verificationState)}
</div>
);
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,8 @@
"group_widgets": "Widgets",
"hidebold": "Hide notification dot (only display counters badges)",
"html_topic": "Show HTML representation of room topics",
"invisible_crypto": "Invisible cryptography",
"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.",
"join_beta": "Join the beta",
"join_beta_reload": "Joining the beta will reload %(brand)s.",
"jump_to_date": "Jump to date (adds /jumptodate and jump to date headers)",
Expand Down Expand Up @@ -3291,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
11 changes: 11 additions & 0 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details.
import React, { ReactNode } from "react";

import { _t, _td, TranslationKey } from "../languageHandler";
import InvisibleCryptoController from "./controllers/InvisibleCryptoController";
import {
NotificationBodyEnabledController,
NotificationsEnabledController,
Expand Down Expand Up @@ -316,6 +317,16 @@ export const SETTINGS: { [setting: string]: ISetting } = {
supportedLevelsAreOrdered: true,
default: false,

Check failure on line 318 in src/settings/Settings.tsx

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Argument of type '"labs|invisible_crypto_description"' is not assignable to parameter of type '"a11y_jump_first_unread_room" | "cannot_invite_without_identity_server" | "cannot_reach_homeserver" | "cannot_reach_homeserver_detail" | "cant_load_page" | "chat_card_back_action_label" | ... 3462 more ... | "zxcvbn|warnings|wordByItself"'.
},
"feature_invisible_crypto": {
isFeature: true,
labsGroup: LabGroup.Encryption,
controller: new InvisibleCryptoController(),
displayName: _td("labs|invisible_crypto"),
description: _td("labs|invisible_crypto_description"),
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
supportedLevelsAreOrdered: true,
default: false,
},
"useOnlyCurrentProfiles": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td("settings|disable_historical_profile"),
Expand Down
30 changes: 30 additions & 0 deletions src/settings/controllers/InvisibleCryptoController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Copyright 2024 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { CryptoMode } from "matrix-js-sdk/src/crypto-api";

import SettingController from "./SettingController";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import { SettingLevel } from "../SettingLevel";

export default class InvisibleCryptoController extends SettingController {
public onChange(level: SettingLevel, roomId: string, newValue: any): void {
console.log("Setting crypto mode to ", newValue ? 2 : 0);
MatrixClientPeg.safeGet()
.getCrypto()!
.setCryptoMode(newValue ? CryptoMode.Invisible : CryptoMode.Legacy);
}
}

0 comments on commit 034cedc

Please sign in to comment.