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

ElementR: Cross user verification #11364

Merged
merged 10 commits into from
Sep 18, 2023
3 changes: 1 addition & 2 deletions cypress/e2e/crypto/crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ describe("Cryptography", function () {
}

it("creating a DM should work, being e2e-encrypted / user verification", function (this: CryptoTestContext) {
skipIfRustCrypto();
skipIfRustCrypto(); // needs working event shields
cy.bootstrapCrossSigning(aliceCredentials);
startDMWithBob.call(this);
// send first message
Expand All @@ -281,7 +281,6 @@ describe("Cryptography", function () {
});

it("should allow verification when there is no existing DM", function (this: CryptoTestContext) {
skipIfRustCrypto();
cy.bootstrapCrossSigning(aliceCredentials);
autoJoin(this.bob);

Expand Down
2 changes: 1 addition & 1 deletion src/DeviceListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ export default class DeviceListener {

// cross signing isn't enabled - nag to enable it
// There are 3 different toasts for:
if (!(await crypto.getCrossSigningKeyId()) && cli.getStoredCrossSigningForUser(cli.getSafeUserId())) {
if (!(await crypto.getCrossSigningKeyId()) && (await crypto.userHasCrossSigningKeys())) {
// Cross-signing on account but this device doesn't trust the master key (verify this session)
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
this.checkKeyBackupStatus();
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/right_panel/EncryptionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
if (!roomId) {
throw new Error("Unable to create Room for verification");
}
verificationRequest_ = await cli.requestVerificationDM(member.userId, roomId);
verificationRequest_ = await cli.getCrypto()!.requestVerificationDM(member.userId, roomId);
} catch (e) {
console.error("Error starting verification", e);
setRequesting(false);
Expand Down
18 changes: 10 additions & 8 deletions src/components/views/right_panel/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,15 @@ export const disambiguateDevices = (devices: IDevice[]): void => {
}
};

export const getE2EStatus = async (cli: MatrixClient, userId: string, devices: IDevice[]): Promise<E2EStatus> => {
export const getE2EStatus = async (
cli: MatrixClient,
userId: string,
devices: IDevice[],
): Promise<E2EStatus | undefined> => {
const crypto = cli.getCrypto();
if (!crypto) return undefined;
const isMe = userId === cli.getUserId();
const userTrust = cli.checkUserTrust(userId);
const userTrust = await crypto.getUserVerificationStatus(userId);
if (!userTrust.isCrossSigningVerified()) {
return userTrust.wasCrossSigningVerified() ? E2EStatus.Warning : E2EStatus.Normal;
}
Expand All @@ -119,7 +125,7 @@ export const getE2EStatus = async (cli: MatrixClient, userId: string, devices: I
// cross-signing so that other users can then safely trust you.
// For other people's devices, the more general verified check that
// includes locally verified devices can be used.
const deviceTrust = await cli.getCrypto()?.getDeviceVerificationStatus(userId, deviceId);
const deviceTrust = await crypto.getDeviceVerificationStatus(userId, deviceId);
return isMe ? !deviceTrust?.crossSigningVerified : !deviceTrust?.isVerified();
});
return anyDeviceUnverified ? E2EStatus.Warning : E2EStatus.Verified;
Expand Down Expand Up @@ -152,11 +158,7 @@ function useHasCrossSigningKeys(
}
setUpdating(true);
try {
// We call it to populate the user keys and devices
await cli.getCrypto()?.getUserDeviceInfo([member.userId], true);
const xsi = cli.getStoredCrossSigningForUser(member.userId);
const key = xsi && xsi.getId();
return !!key;
return await cli.getCrypto()?.userHasCrossSigningKeys(member.userId, true);
} finally {
setUpdating(false);
}
Expand Down
14 changes: 6 additions & 8 deletions src/utils/ShieldUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,15 @@ export async function shieldStatusForRoom(client: MatrixClient, room: Room): Pro

const verified: string[] = [];
const unverified: string[] = [];
members
.filter((userId) => userId !== client.getUserId())
.forEach((userId) => {
(client.checkUserTrust(userId).isCrossSigningVerified() ? verified : unverified).push(userId);
});
for (const userId of members) {
if (userId === client.getUserId()) continue;
const userTrust = await crypto.getUserVerificationStatus(userId);

/* Alarm if any unverified users were verified before. */
for (const userId of unverified) {
if (client.checkUserTrust(userId).wasCrossSigningVerified()) {
/* Alarm if any unverified users were verified before. */
if (userTrust.wasCrossSigningVerified() && !userTrust.isCrossSigningVerified()) {
return E2EStatus.Warning;
}
(userTrust.isCrossSigningVerified() ? verified : unverified).push(userId);
}

/* Check all verified user devices. */
Expand Down
7 changes: 3 additions & 4 deletions test/DeviceListener-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
ClientStoppedError,
} from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";

Expand Down Expand Up @@ -92,6 +91,7 @@ describe("DeviceListener", () => {
getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
isCrossSigningReady: jest.fn().mockResolvedValue(true),
isSecretStorageReady: jest.fn().mockResolvedValue(true),
userHasCrossSigningKeys: jest.fn(),
} as unknown as Mocked<CryptoApi>;
mockClient = getMockClientWithEventEmitter({
isGuest: jest.fn(),
Expand All @@ -102,7 +102,6 @@ describe("DeviceListener", () => {
isVersionSupported: jest.fn().mockResolvedValue(true),
isInitialSyncComplete: jest.fn().mockReturnValue(true),
getKeyBackupEnabled: jest.fn(),
getStoredCrossSigningForUser: jest.fn(),
waitForClientWellKnown: jest.fn(),
isRoomEncrypted: jest.fn(),
getClientWellKnown: jest.fn(),
Expand Down Expand Up @@ -324,7 +323,7 @@ describe("DeviceListener", () => {
});

it("shows verify session toast when account has cross signing", async () => {
mockClient!.getStoredCrossSigningForUser.mockReturnValue(new CrossSigningInfo(userId));
mockCrypto!.userHasCrossSigningKeys.mockResolvedValue(true);
await createAndStart();

expect(mockCrypto!.getUserDeviceInfo).toHaveBeenCalled();
Expand All @@ -335,7 +334,7 @@ describe("DeviceListener", () => {

it("checks key backup status when when account has cross signing", async () => {
mockCrypto!.getCrossSigningKeyId.mockResolvedValue(null);
mockClient!.getStoredCrossSigningForUser.mockReturnValue(new CrossSigningInfo(userId));
mockCrypto!.userHasCrossSigningKeys.mockResolvedValue(true);
await createAndStart();

expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalled();
Expand Down
6 changes: 5 additions & 1 deletion test/components/views/right_panel/UserInfo-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
import { UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
import { defer } from "matrix-js-sdk/src/utils";
import { EventEmitter } from "events";
import { UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";

import UserInfo, {
BanToggleButton,
Expand Down Expand Up @@ -134,6 +135,8 @@ beforeEach(() => {
mockCrypto = mocked({
getDeviceVerificationStatus: jest.fn(),
getUserDeviceInfo: jest.fn(),
userHasCrossSigningKeys: jest.fn().mockResolvedValue(false),
getUserVerificationStatus: jest.fn(),
} as unknown as CryptoApi);

mockClient = mocked({
Expand Down Expand Up @@ -161,7 +164,6 @@ beforeEach(() => {
setPowerLevel: jest.fn(),
downloadKeys: jest.fn(),
getCrypto: jest.fn().mockReturnValue(mockCrypto),
getStoredCrossSigningForUser: jest.fn(),
} as unknown as MatrixClient);

jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
Expand Down Expand Up @@ -378,6 +380,7 @@ describe("<UserInfo />", () => {

it("renders unverified user info", async () => {
mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(false, false, false));
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
renderComponent({ room: mockRoom });
await act(flushPromises);

Expand All @@ -389,6 +392,7 @@ describe("<UserInfo />", () => {

it("renders verified user info", async () => {
mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(true, false, false));
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, false, false));
renderComponent({ room: mockRoom });
await act(flushPromises);

Expand Down
7 changes: 3 additions & 4 deletions test/utils/ShieldUtils-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";

import { shieldStatusForRoom } from "../../src/utils/ShieldUtils";
import DMRoomMap from "../../src/utils/DMRoomMap";
Expand All @@ -30,10 +31,8 @@ function mkClient(selfTrust = false) {
getUserDeviceInfo: async (userIds: string[]) => {
return new Map(userIds.map((u) => [u, new Map([["DEVICE", {}]])]));
},
}),
checkUserTrust: (userId: string) => ({
isCrossSigningVerified: () => userId[1] == "T",
wasCrossSigningVerified: () => userId[1] == "T" || userId[1] == "W",
getUserVerificationStatus: async (userId: string): Promise<UserVerificationStatus> =>
new UserVerificationStatus(userId[1] == "T", userId[1] == "T" || userId[1] == "W", false),
}),
} as unknown as MatrixClient;
}
Expand Down