ElementR: Cross user verification (#11364)
The hard work has been done in previous PRs in the js-sdk, so this is now just a case of updating a few call sites to use the new APIs.
This commit is contained in:
parent
e887c6d71f
commit
579b0dd10a
8 changed files with 33 additions and 31 deletions
|
@ -259,7 +259,7 @@ describe("Cryptography", function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("creating a DM should work, being e2e-encrypted / user verification", function (this: CryptoTestContext) {
|
it("creating a DM should work, being e2e-encrypted / user verification", function (this: CryptoTestContext) {
|
||||||
skipIfRustCrypto();
|
skipIfRustCrypto(); // needs working event shields
|
||||||
cy.bootstrapCrossSigning(aliceCredentials);
|
cy.bootstrapCrossSigning(aliceCredentials);
|
||||||
startDMWithBob.call(this);
|
startDMWithBob.call(this);
|
||||||
// send first message
|
// send first message
|
||||||
|
@ -281,7 +281,6 @@ describe("Cryptography", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow verification when there is no existing DM", function (this: CryptoTestContext) {
|
it("should allow verification when there is no existing DM", function (this: CryptoTestContext) {
|
||||||
skipIfRustCrypto();
|
|
||||||
cy.bootstrapCrossSigning(aliceCredentials);
|
cy.bootstrapCrossSigning(aliceCredentials);
|
||||||
autoJoin(this.bob);
|
autoJoin(this.bob);
|
||||||
|
|
||||||
|
|
|
@ -307,7 +307,7 @@ export default class DeviceListener {
|
||||||
|
|
||||||
// cross signing isn't enabled - nag to enable it
|
// cross signing isn't enabled - nag to enable it
|
||||||
// There are 3 different toasts for:
|
// 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)
|
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
||||||
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
||||||
this.checkKeyBackupStatus();
|
this.checkKeyBackupStatus();
|
||||||
|
|
|
@ -110,7 +110,7 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
|
||||||
if (!roomId) {
|
if (!roomId) {
|
||||||
throw new Error("Unable to create Room for verification");
|
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) {
|
} catch (e) {
|
||||||
console.error("Error starting verification", e);
|
console.error("Error starting verification", e);
|
||||||
setRequesting(false);
|
setRequesting(false);
|
||||||
|
|
|
@ -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 isMe = userId === cli.getUserId();
|
||||||
const userTrust = cli.checkUserTrust(userId);
|
const userTrust = await crypto.getUserVerificationStatus(userId);
|
||||||
if (!userTrust.isCrossSigningVerified()) {
|
if (!userTrust.isCrossSigningVerified()) {
|
||||||
return userTrust.wasCrossSigningVerified() ? E2EStatus.Warning : E2EStatus.Normal;
|
return userTrust.wasCrossSigningVerified() ? E2EStatus.Warning : E2EStatus.Normal;
|
||||||
}
|
}
|
||||||
|
@ -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.
|
// cross-signing so that other users can then safely trust you.
|
||||||
// For other people's devices, the more general verified check that
|
// For other people's devices, the more general verified check that
|
||||||
// includes locally verified devices can be used.
|
// 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 isMe ? !deviceTrust?.crossSigningVerified : !deviceTrust?.isVerified();
|
||||||
});
|
});
|
||||||
return anyDeviceUnverified ? E2EStatus.Warning : E2EStatus.Verified;
|
return anyDeviceUnverified ? E2EStatus.Warning : E2EStatus.Verified;
|
||||||
|
@ -152,11 +158,7 @@ function useHasCrossSigningKeys(
|
||||||
}
|
}
|
||||||
setUpdating(true);
|
setUpdating(true);
|
||||||
try {
|
try {
|
||||||
// We call it to populate the user keys and devices
|
return await cli.getCrypto()?.userHasCrossSigningKeys(member.userId, true);
|
||||||
await cli.getCrypto()?.getUserDeviceInfo([member.userId], true);
|
|
||||||
const xsi = cli.getStoredCrossSigningForUser(member.userId);
|
|
||||||
const key = xsi && xsi.getId();
|
|
||||||
return !!key;
|
|
||||||
} finally {
|
} finally {
|
||||||
setUpdating(false);
|
setUpdating(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,17 +37,15 @@ export async function shieldStatusForRoom(client: MatrixClient, room: Room): Pro
|
||||||
|
|
||||||
const verified: string[] = [];
|
const verified: string[] = [];
|
||||||
const unverified: string[] = [];
|
const unverified: string[] = [];
|
||||||
members
|
for (const userId of members) {
|
||||||
.filter((userId) => userId !== client.getUserId())
|
if (userId === client.getUserId()) continue;
|
||||||
.forEach((userId) => {
|
const userTrust = await crypto.getUserVerificationStatus(userId);
|
||||||
(client.checkUserTrust(userId).isCrossSigningVerified() ? verified : unverified).push(userId);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Alarm if any unverified users were verified before. */
|
/* Alarm if any unverified users were verified before. */
|
||||||
for (const userId of unverified) {
|
if (userTrust.wasCrossSigningVerified() && !userTrust.isCrossSigningVerified()) {
|
||||||
if (client.checkUserTrust(userId).wasCrossSigningVerified()) {
|
|
||||||
return E2EStatus.Warning;
|
return E2EStatus.Warning;
|
||||||
}
|
}
|
||||||
|
(userTrust.isCrossSigningVerified() ? verified : unverified).push(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check all verified user devices. */
|
/* Check all verified user devices. */
|
||||||
|
|
|
@ -25,7 +25,6 @@ import {
|
||||||
ClientStoppedError,
|
ClientStoppedError,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
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 { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||||
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
|
|
||||||
|
@ -92,6 +91,7 @@ describe("DeviceListener", () => {
|
||||||
getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
|
getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
|
||||||
isCrossSigningReady: jest.fn().mockResolvedValue(true),
|
isCrossSigningReady: jest.fn().mockResolvedValue(true),
|
||||||
isSecretStorageReady: jest.fn().mockResolvedValue(true),
|
isSecretStorageReady: jest.fn().mockResolvedValue(true),
|
||||||
|
userHasCrossSigningKeys: jest.fn(),
|
||||||
} as unknown as Mocked<CryptoApi>;
|
} as unknown as Mocked<CryptoApi>;
|
||||||
mockClient = getMockClientWithEventEmitter({
|
mockClient = getMockClientWithEventEmitter({
|
||||||
isGuest: jest.fn(),
|
isGuest: jest.fn(),
|
||||||
|
@ -102,7 +102,6 @@ describe("DeviceListener", () => {
|
||||||
isVersionSupported: jest.fn().mockResolvedValue(true),
|
isVersionSupported: jest.fn().mockResolvedValue(true),
|
||||||
isInitialSyncComplete: jest.fn().mockReturnValue(true),
|
isInitialSyncComplete: jest.fn().mockReturnValue(true),
|
||||||
getKeyBackupEnabled: jest.fn(),
|
getKeyBackupEnabled: jest.fn(),
|
||||||
getStoredCrossSigningForUser: jest.fn(),
|
|
||||||
waitForClientWellKnown: jest.fn(),
|
waitForClientWellKnown: jest.fn(),
|
||||||
isRoomEncrypted: jest.fn(),
|
isRoomEncrypted: jest.fn(),
|
||||||
getClientWellKnown: jest.fn(),
|
getClientWellKnown: jest.fn(),
|
||||||
|
@ -324,7 +323,7 @@ describe("DeviceListener", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows verify session toast when account has cross signing", async () => {
|
it("shows verify session toast when account has cross signing", async () => {
|
||||||
mockClient!.getStoredCrossSigningForUser.mockReturnValue(new CrossSigningInfo(userId));
|
mockCrypto!.userHasCrossSigningKeys.mockResolvedValue(true);
|
||||||
await createAndStart();
|
await createAndStart();
|
||||||
|
|
||||||
expect(mockCrypto!.getUserDeviceInfo).toHaveBeenCalled();
|
expect(mockCrypto!.getUserDeviceInfo).toHaveBeenCalled();
|
||||||
|
@ -335,7 +334,7 @@ describe("DeviceListener", () => {
|
||||||
|
|
||||||
it("checks key backup status when when account has cross signing", async () => {
|
it("checks key backup status when when account has cross signing", async () => {
|
||||||
mockCrypto!.getCrossSigningKeyId.mockResolvedValue(null);
|
mockCrypto!.getCrossSigningKeyId.mockResolvedValue(null);
|
||||||
mockClient!.getStoredCrossSigningForUser.mockReturnValue(new CrossSigningInfo(userId));
|
mockCrypto!.userHasCrossSigningKeys.mockResolvedValue(true);
|
||||||
await createAndStart();
|
await createAndStart();
|
||||||
|
|
||||||
expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalled();
|
expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalled();
|
||||||
|
|
|
@ -37,6 +37,7 @@ import {
|
||||||
import { UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
import { UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||||
import { defer } from "matrix-js-sdk/src/utils";
|
import { defer } from "matrix-js-sdk/src/utils";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
import { UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||||
|
|
||||||
import UserInfo, {
|
import UserInfo, {
|
||||||
BanToggleButton,
|
BanToggleButton,
|
||||||
|
@ -134,6 +135,8 @@ beforeEach(() => {
|
||||||
mockCrypto = mocked({
|
mockCrypto = mocked({
|
||||||
getDeviceVerificationStatus: jest.fn(),
|
getDeviceVerificationStatus: jest.fn(),
|
||||||
getUserDeviceInfo: jest.fn(),
|
getUserDeviceInfo: jest.fn(),
|
||||||
|
userHasCrossSigningKeys: jest.fn().mockResolvedValue(false),
|
||||||
|
getUserVerificationStatus: jest.fn(),
|
||||||
} as unknown as CryptoApi);
|
} as unknown as CryptoApi);
|
||||||
|
|
||||||
mockClient = mocked({
|
mockClient = mocked({
|
||||||
|
@ -161,7 +164,6 @@ beforeEach(() => {
|
||||||
setPowerLevel: jest.fn(),
|
setPowerLevel: jest.fn(),
|
||||||
downloadKeys: jest.fn(),
|
downloadKeys: jest.fn(),
|
||||||
getCrypto: jest.fn().mockReturnValue(mockCrypto),
|
getCrypto: jest.fn().mockReturnValue(mockCrypto),
|
||||||
getStoredCrossSigningForUser: jest.fn(),
|
|
||||||
} as unknown as MatrixClient);
|
} as unknown as MatrixClient);
|
||||||
|
|
||||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||||
|
@ -378,6 +380,7 @@ describe("<UserInfo />", () => {
|
||||||
|
|
||||||
it("renders unverified user info", async () => {
|
it("renders unverified user info", async () => {
|
||||||
mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(false, false, false));
|
mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(false, false, false));
|
||||||
|
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||||
renderComponent({ room: mockRoom });
|
renderComponent({ room: mockRoom });
|
||||||
await act(flushPromises);
|
await act(flushPromises);
|
||||||
|
|
||||||
|
@ -389,6 +392,7 @@ describe("<UserInfo />", () => {
|
||||||
|
|
||||||
it("renders verified user info", async () => {
|
it("renders verified user info", async () => {
|
||||||
mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(true, false, false));
|
mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(true, false, false));
|
||||||
|
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, false, false));
|
||||||
renderComponent({ room: mockRoom });
|
renderComponent({ room: mockRoom });
|
||||||
await act(flushPromises);
|
await act(flushPromises);
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
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 { shieldStatusForRoom } from "../../src/utils/ShieldUtils";
|
||||||
import DMRoomMap from "../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../src/utils/DMRoomMap";
|
||||||
|
@ -30,10 +31,8 @@ function mkClient(selfTrust = false) {
|
||||||
getUserDeviceInfo: async (userIds: string[]) => {
|
getUserDeviceInfo: async (userIds: string[]) => {
|
||||||
return new Map(userIds.map((u) => [u, new Map([["DEVICE", {}]])]));
|
return new Map(userIds.map((u) => [u, new Map([["DEVICE", {}]])]));
|
||||||
},
|
},
|
||||||
}),
|
getUserVerificationStatus: async (userId: string): Promise<UserVerificationStatus> =>
|
||||||
checkUserTrust: (userId: string) => ({
|
new UserVerificationStatus(userId[1] == "T", userId[1] == "T" || userId[1] == "W", false),
|
||||||
isCrossSigningVerified: () => userId[1] == "T",
|
|
||||||
wasCrossSigningVerified: () => userId[1] == "T" || userId[1] == "W",
|
|
||||||
}),
|
}),
|
||||||
} as unknown as MatrixClient;
|
} as unknown as MatrixClient;
|
||||||
}
|
}
|
||||||
|
@ -50,8 +49,9 @@ describe("mkClient self-test", function () {
|
||||||
["@TF:h", true],
|
["@TF:h", true],
|
||||||
["@FT:h", false],
|
["@FT:h", false],
|
||||||
["@FF:h", false],
|
["@FF:h", false],
|
||||||
])("behaves well for user trust %s", (userId, trust) => {
|
])("behaves well for user trust %s", async (userId, trust) => {
|
||||||
expect(mkClient().checkUserTrust(userId).isCrossSigningVerified()).toBe(trust);
|
const status = await mkClient().getCrypto()?.getUserVerificationStatus(userId);
|
||||||
|
expect(status!.isCrossSigningVerified()).toBe(trust);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
|
|
Loading…
Reference in a new issue