You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
New CryptoApi.getDeviceVerificationStatus
api (#3287)
* Element-R: implement `{get,set}TrustCrossSignedDevices` A precursor to https://github.com/vector-im/element-web/issues/25092 * Pull out new `DeviceVerificationStatus` Define a new base class to replace `DeviceTrustLevel`. The intention is to have a cleaner interface which is easier to expose from the new crypto impl * Define, and implement, a new `CryptoApi.getDeviceVerificationStatus` This is similar to `checkDeviceTrust`, which we're deprecating, but: * is `async`, meaning we can implement it in Rust * Returns a `DeviceVerificationStatus` instead of a `DeviceTrustLevel` * Returns `null` rather than "not verified" if the device is unknown * add some tests * Export DeviceVerificationStatus as a proper class ... so that we can instantiate it in tests
This commit is contained in:
committed by
GitHub
parent
8c30a3b0df
commit
a03438f2af
@ -374,6 +374,12 @@ describe("SAS verification", function () {
|
||||
expect(bobDeviceTrust.isLocallyVerified()).toBeTruthy();
|
||||
expect(bobDeviceTrust.isCrossSigningVerified()).toBeFalsy();
|
||||
|
||||
const bobDeviceVerificationStatus = (await alice.client
|
||||
.getCrypto()!
|
||||
.getDeviceVerificationStatus("@bob:example.com", "Dynabook"))!;
|
||||
expect(bobDeviceVerificationStatus.localVerified).toBe(true);
|
||||
expect(bobDeviceVerificationStatus.crossSigningVerified).toBe(false);
|
||||
|
||||
const aliceTrust = bob.client.checkUserTrust("@alice:example.com");
|
||||
expect(aliceTrust.isCrossSigningVerified()).toBeTruthy();
|
||||
expect(aliceTrust.isTofu()).toBeTruthy();
|
||||
@ -381,6 +387,17 @@ describe("SAS verification", function () {
|
||||
const aliceDeviceTrust = bob.client.checkDeviceTrust("@alice:example.com", "Osborne2");
|
||||
expect(aliceDeviceTrust.isLocallyVerified()).toBeTruthy();
|
||||
expect(aliceDeviceTrust.isCrossSigningVerified()).toBeFalsy();
|
||||
|
||||
const aliceDeviceVerificationStatus = (await bob.client
|
||||
.getCrypto()!
|
||||
.getDeviceVerificationStatus("@alice:example.com", "Osborne2"))!;
|
||||
expect(aliceDeviceVerificationStatus.localVerified).toBe(true);
|
||||
expect(aliceDeviceVerificationStatus.crossSigningVerified).toBe(false);
|
||||
|
||||
const unknownDeviceVerificationStatus = await bob.client
|
||||
.getCrypto()!
|
||||
.getDeviceVerificationStatus("@alice:example.com", "xyz");
|
||||
expect(unknownDeviceVerificationStatus).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -249,4 +249,34 @@ describe("RustCrypto", () => {
|
||||
expect(rustCrypto.getTrustCrossSignedDevices()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDeviceVerificationStatus", () => {
|
||||
let rustCrypto: RustCrypto;
|
||||
let olmMachine: Mocked<RustSdkCryptoJs.OlmMachine>;
|
||||
|
||||
beforeEach(() => {
|
||||
olmMachine = {
|
||||
getDevice: jest.fn(),
|
||||
} as unknown as Mocked<RustSdkCryptoJs.OlmMachine>;
|
||||
rustCrypto = new RustCrypto(olmMachine, {} as MatrixClient["http"], TEST_USER, TEST_DEVICE_ID);
|
||||
});
|
||||
|
||||
it("should call getDevice", async () => {
|
||||
olmMachine.getDevice.mockResolvedValue({
|
||||
isCrossSigningTrusted: jest.fn().mockReturnValue(false),
|
||||
isLocallyTrusted: jest.fn().mockReturnValue(false),
|
||||
} as unknown as RustSdkCryptoJs.Device);
|
||||
const res = await rustCrypto.getDeviceVerificationStatus("@user:domain", "device");
|
||||
expect(olmMachine.getDevice.mock.calls[0][0].toString()).toEqual("@user:domain");
|
||||
expect(olmMachine.getDevice.mock.calls[0][1].toString()).toEqual("device");
|
||||
expect(res?.crossSigningVerified).toBe(false);
|
||||
expect(res?.localVerified).toBe(false);
|
||||
});
|
||||
|
||||
it("should return null for unknown device", async () => {
|
||||
olmMachine.getDevice.mockResolvedValue(undefined);
|
||||
const res = await rustCrypto.getDeviceVerificationStatus("@user:domain", "device");
|
||||
expect(res).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2621,12 +2621,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @param userId - The ID of the user whose devices is to be checked.
|
||||
* @param deviceId - The ID of the device to check
|
||||
*
|
||||
* @deprecated Use {@link CryptoApi.getDeviceVerificationStatus | `CryptoApi.getDeviceVerificationStatus`}
|
||||
*/
|
||||
public checkDeviceTrust(userId: string, deviceId: string): DeviceTrustLevel {
|
||||
if (!this.cryptoBackend) {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
return this.cryptoBackend.checkDeviceTrust(userId, deviceId);
|
||||
return this.crypto.checkDeviceTrust(userId, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,7 +18,7 @@ import type { IDeviceLists, IToDeviceEvent } from "../sync-accumulator";
|
||||
import { MatrixEvent } from "../models/event";
|
||||
import { Room } from "../models/room";
|
||||
import { CryptoApi } from "../crypto-api";
|
||||
import { DeviceTrustLevel, UserTrustLevel } from "../crypto/CrossSigning";
|
||||
import { UserTrustLevel } from "../crypto/CrossSigning";
|
||||
import { IEncryptedEventInfo } from "../crypto/api";
|
||||
import { IEventDecryptionResult } from "../@types/crypto";
|
||||
|
||||
@ -51,16 +51,6 @@ export interface CryptoBackend extends SyncCryptoCallbacks, CryptoApi {
|
||||
*/
|
||||
checkUserTrust(userId: string): UserTrustLevel;
|
||||
|
||||
/**
|
||||
* Get the verification level for a given device
|
||||
*
|
||||
* TODO: define this better
|
||||
*
|
||||
* @param userId - user to be checked
|
||||
* @param deviceId - device to be checked
|
||||
*/
|
||||
checkDeviceTrust(userId: string, deviceId: string): DeviceTrustLevel;
|
||||
|
||||
/**
|
||||
* Encrypt an event according to the configuration of the room.
|
||||
*
|
||||
|
@ -93,4 +93,54 @@ export interface CryptoApi {
|
||||
* @returns `true` if we trust cross-signed devices, otherwise `false`.
|
||||
*/
|
||||
getTrustCrossSignedDevices(): boolean;
|
||||
|
||||
/**
|
||||
* Get the verification status of a given device.
|
||||
*
|
||||
* @param userId - The ID of the user whose device is to be checked.
|
||||
* @param deviceId - The ID of the device to check
|
||||
*
|
||||
* @returns Verification status of the device, or `null` if the device is not known
|
||||
*/
|
||||
getDeviceVerificationStatus(userId: string, deviceId: string): Promise<DeviceVerificationStatus | null>;
|
||||
}
|
||||
|
||||
export class DeviceVerificationStatus {
|
||||
public constructor(
|
||||
/**
|
||||
* True if this device has been verified via cross signing.
|
||||
*
|
||||
* This does *not* take into account `trustCrossSignedDevices`.
|
||||
*/
|
||||
public readonly crossSigningVerified: boolean,
|
||||
|
||||
/**
|
||||
* TODO: tofu magic wtf does this do?
|
||||
*/
|
||||
public readonly tofu: boolean,
|
||||
|
||||
/**
|
||||
* True if the device has been marked as locally verified.
|
||||
*/
|
||||
public readonly localVerified: boolean,
|
||||
|
||||
/**
|
||||
* True if the client has been configured to trust cross-signed devices via {@link CryptoApi#setTrustCrossSignedDevices}.
|
||||
*/
|
||||
private readonly trustCrossSignedDevices: boolean,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Check if we should consider this device "verified".
|
||||
*
|
||||
* A device is "verified" if either:
|
||||
* * it has been manually marked as such via {@link MatrixClient#setDeviceVerified}.
|
||||
* * it has been cross-signed with a verified signing key, **and** the client has been configured to trust
|
||||
* cross-signed devices via {@link CryptoApi#setTrustCrossSignedDevices}.
|
||||
*
|
||||
* @returns true if this device is verified via any means.
|
||||
*/
|
||||
public isVerified(): boolean {
|
||||
return this.localVerified || (this.trustCrossSignedDevices && this.crossSigningVerified);
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import { ICryptoCallbacks } from ".";
|
||||
import { ISignatures } from "../@types/signed";
|
||||
import { CryptoStore, SecretStorePrivateKeys } from "./store/base";
|
||||
import { ServerSideSecretStorage, SecretStorageKeyDescription } from "../secret-storage";
|
||||
import { DeviceVerificationStatus } from "../crypto-api";
|
||||
|
||||
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
|
||||
|
||||
@ -628,16 +629,11 @@ export class UserTrustLevel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the ways in which we trust a device
|
||||
* Represents the ways in which we trust a device.
|
||||
*
|
||||
* @deprecated Use {@link DeviceVerificationStatus}.
|
||||
*/
|
||||
export class DeviceTrustLevel {
|
||||
public constructor(
|
||||
public readonly crossSigningVerified: boolean,
|
||||
public readonly tofu: boolean,
|
||||
private readonly localVerified: boolean,
|
||||
private readonly trustCrossSignedDevices: boolean,
|
||||
) {}
|
||||
|
||||
export class DeviceTrustLevel extends DeviceVerificationStatus {
|
||||
public static fromUserTrustLevel(
|
||||
userTrustLevel: UserTrustLevel,
|
||||
localVerified: boolean,
|
||||
@ -651,13 +647,6 @@ export class DeviceTrustLevel {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if this device is verified via any means
|
||||
*/
|
||||
public isVerified(): boolean {
|
||||
return Boolean(this.isLocallyVerified() || (this.trustCrossSignedDevices && this.isCrossSigningVerified()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if this device is verified via cross signing
|
||||
*/
|
||||
|
@ -88,6 +88,7 @@ import {
|
||||
ServerSideSecretStorageImpl,
|
||||
} from "../secret-storage";
|
||||
import { ISecretRequest } from "./SecretSharing";
|
||||
import { DeviceVerificationStatus } from "../crypto-api";
|
||||
|
||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
@ -1452,10 +1453,22 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
/**
|
||||
* Check whether a given device is trusted.
|
||||
*
|
||||
* @param userId - The ID of the user whose devices is to be checked.
|
||||
* @param userId - The ID of the user whose device is to be checked.
|
||||
* @param deviceId - The ID of the device to check
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
public async getDeviceVerificationStatus(
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
): Promise<DeviceVerificationStatus | null> {
|
||||
const device = this.deviceList.getStoredDevice(userId, deviceId);
|
||||
if (!device) {
|
||||
return null;
|
||||
}
|
||||
return this.checkDeviceInfoTrust(userId, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link CryptoApi.getDeviceVerificationStatus}.
|
||||
*/
|
||||
public checkDeviceTrust(userId: string, deviceId: string): DeviceTrustLevel {
|
||||
const device = this.deviceList.getStoredDevice(userId, deviceId);
|
||||
@ -1468,7 +1481,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* @param userId - The ID of the user whose devices is to be checked.
|
||||
* @param device - The device info object to check
|
||||
*
|
||||
* @returns
|
||||
* @deprecated Use {@link CryptoApi.getDeviceVerificationStatus}.
|
||||
*/
|
||||
public checkDeviceInfoTrust(userId: string, device?: DeviceInfo): DeviceTrustLevel {
|
||||
const trustedLocally = !!device?.isVerified();
|
||||
|
@ -63,6 +63,7 @@ export type { MatrixCall } from "./webrtc/call";
|
||||
export { GroupCallEvent, GroupCallIntent, GroupCallState, GroupCallType } from "./webrtc/groupCall";
|
||||
export type { GroupCall } from "./webrtc/groupCall";
|
||||
export type { CryptoApi } from "./crypto-api";
|
||||
export { DeviceVerificationStatus } from "./crypto-api";
|
||||
export { CryptoEvent } from "./crypto";
|
||||
|
||||
let cryptoStoreFactory = (): CryptoStore => new MemoryCryptoStore();
|
||||
|
@ -25,11 +25,12 @@ import { RoomMember } from "../models/room-member";
|
||||
import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
|
||||
import { logger } from "../logger";
|
||||
import { IHttpOpts, MatrixHttpApi } from "../http-api";
|
||||
import { DeviceTrustLevel, UserTrustLevel } from "../crypto/CrossSigning";
|
||||
import { UserTrustLevel } from "../crypto/CrossSigning";
|
||||
import { RoomEncryptor } from "./RoomEncryptor";
|
||||
import { OutgoingRequest, OutgoingRequestProcessor } from "./OutgoingRequestProcessor";
|
||||
import { KeyClaimManager } from "./KeyClaimManager";
|
||||
import { MapWithDefault } from "../utils";
|
||||
import { DeviceVerificationStatus } from "../crypto-api";
|
||||
|
||||
/**
|
||||
* An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto.
|
||||
@ -131,11 +132,6 @@ export class RustCrypto implements CryptoBackend {
|
||||
return new UserTrustLevel(false, false, false);
|
||||
}
|
||||
|
||||
public checkDeviceTrust(userId: string, deviceId: string): DeviceTrustLevel {
|
||||
// TODO
|
||||
return new DeviceTrustLevel(false, false, false, false);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// CryptoApi implementation
|
||||
@ -182,6 +178,28 @@ export class RustCrypto implements CryptoBackend {
|
||||
// events. Maybe we need to do the same?
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link CryptoApi#getDeviceVerificationStatus}.
|
||||
*/
|
||||
public async getDeviceVerificationStatus(
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
): Promise<DeviceVerificationStatus | null> {
|
||||
const device: RustSdkCryptoJs.Device | undefined = await this.olmMachine.getDevice(
|
||||
new RustSdkCryptoJs.UserId(userId),
|
||||
new RustSdkCryptoJs.DeviceId(deviceId),
|
||||
);
|
||||
|
||||
if (!device) return null;
|
||||
|
||||
return new DeviceVerificationStatus(
|
||||
device.isCrossSigningTrusted(),
|
||||
false, // tofu
|
||||
device.isLocallyTrusted(),
|
||||
this._trustCrossSignedDevices,
|
||||
);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SyncCryptoCallbacks implementation
|
||||
|
Reference in New Issue
Block a user