diff --git a/spec/unit/crypto/verification/sas.spec.ts b/spec/unit/crypto/verification/sas.spec.ts index c78dfeba237..1ea2c322285 100644 --- a/spec/unit/crypto/verification/sas.spec.ts +++ b/spec/unit/crypto/verification/sas.spec.ts @@ -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); }); }); diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index 3eea4ba12b7..5fb73ea0723 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -249,4 +249,34 @@ describe("RustCrypto", () => { expect(rustCrypto.getTrustCrossSignedDevices()).toBe(true); }); }); + + describe("getDeviceVerificationStatus", () => { + let rustCrypto: RustCrypto; + let olmMachine: Mocked; + + beforeEach(() => { + olmMachine = { + getDevice: jest.fn(), + } as unknown as Mocked; + 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); + }); + }); }); diff --git a/src/client.ts b/src/client.ts index 846e7ebbc85..5bc95837508 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2621,12 +2621,14 @@ export class MatrixClient extends TypedEventEmitter; +} + +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); + } } diff --git a/src/crypto/CrossSigning.ts b/src/crypto/CrossSigning.ts index 6696024cd2f..8d184dbb36f 100644 --- a/src/crypto/CrossSigning.ts +++ b/src/crypto/CrossSigning.ts @@ -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 */ diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 1bc6d0bfeff..02df829f6ed 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -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 { + 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 new MemoryCryptoStore(); diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 2c79f9e9ea2..6754da8f585 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -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 { + 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