1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-31 15:24:23 +03:00

ElementR: Update CryptoApi.userHasCrossSigningKeys (#3646)

* WIP `CryptoApi.getStoredCrossSigningForUser`

* Fix QRCode

* Add docs and rename

* Add tests for `RustCrossSigningInfo.ts`

* Do `/keys/query` instead of using `UserIdentity`

* Review changes

* Get rid of `CrossSigningInfo`

* Merge `hasCrossSigningKeysForUser` into `userHasCrossSigningKeys`

* Apply suggestions from code review

* More review comments

---------

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Co-authored-by: Richard van der Hoff <richard@matrix.org>
This commit is contained in:
Florian Duros
2023-08-29 13:27:28 +02:00
committed by GitHub
parent 4c00b41046
commit dec4650d3d
7 changed files with 132 additions and 29 deletions

View File

@ -25,7 +25,16 @@ import Olm from "@matrix-org/olm";
import type { IDeviceKeys } from "../../../src/@types/crypto"; import type { IDeviceKeys } from "../../../src/@types/crypto";
import * as testUtils from "../../test-utils/test-utils"; import * as testUtils from "../../test-utils/test-utils";
import { CRYPTO_BACKENDS, getSyncResponse, InitCrypto, syncPromise } from "../../test-utils/test-utils"; import { CRYPTO_BACKENDS, getSyncResponse, InitCrypto, syncPromise } from "../../test-utils/test-utils";
import { TEST_ROOM_ID, TEST_ROOM_ID as ROOM_ID, TEST_USER_ID } from "../../test-utils/test-data"; import {
BOB_SIGNED_CROSS_SIGNING_KEYS_DATA,
BOB_SIGNED_TEST_DEVICE_DATA,
BOB_TEST_USER_ID,
SIGNED_CROSS_SIGNING_KEYS_DATA,
SIGNED_TEST_DEVICE_DATA,
TEST_ROOM_ID,
TEST_ROOM_ID as ROOM_ID,
TEST_USER_ID,
} from "../../test-utils/test-data";
import { TestClient } from "../../TestClient"; import { TestClient } from "../../TestClient";
import { logger } from "../../../src/logger"; import { logger } from "../../../src/logger";
import { import {
@ -36,6 +45,7 @@ import {
IDownloadKeyResult, IDownloadKeyResult,
IEvent, IEvent,
IndexedDBCryptoStore, IndexedDBCryptoStore,
IRoomEvent,
IStartClientOpts, IStartClientOpts,
MatrixClient, MatrixClient,
MatrixEvent, MatrixEvent,
@ -44,7 +54,6 @@ import {
Room, Room,
RoomMember, RoomMember,
RoomStateEvent, RoomStateEvent,
IRoomEvent,
} from "../../../src/matrix"; } from "../../../src/matrix";
import { DeviceInfo } from "../../../src/crypto/deviceinfo"; import { DeviceInfo } from "../../../src/crypto/deviceinfo";
import { E2EKeyReceiver, IE2EKeyReceiver } from "../../test-utils/E2EKeyReceiver"; import { E2EKeyReceiver, IE2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
@ -2566,4 +2575,46 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
}, },
); );
}); });
describe("Check if the cross signing keys are available for a user", () => {
beforeEach(async () => {
// anything that we don't have a specific matcher for silently returns a 404
fetchMock.catch(404);
keyResponder = new E2EKeyResponder(aliceClient.getHomeserverUrl());
keyResponder.addCrossSigningData(SIGNED_CROSS_SIGNING_KEYS_DATA);
keyResponder.addDeviceKeys(SIGNED_TEST_DEVICE_DATA);
keyResponder.addKeyReceiver(BOB_TEST_USER_ID, keyReceiver);
keyResponder.addCrossSigningData(BOB_SIGNED_CROSS_SIGNING_KEYS_DATA);
keyResponder.addDeviceKeys(BOB_SIGNED_TEST_DEVICE_DATA);
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
await startClientAndAwaitFirstSync();
});
it("Cross signing keys are available for an untracked user with cross signing keys on the homeserver", async () => {
// Needed for old crypto, download and cache locally the cross signing keys of Bob
await aliceClient.getCrypto()?.getUserDeviceInfo([BOB_TEST_USER_ID], true);
const hasCrossSigningKeysForUser = await aliceClient
.getCrypto()!
.userHasCrossSigningKeys(BOB_TEST_USER_ID, true);
expect(hasCrossSigningKeysForUser).toBe(true);
});
it("Cross signing keys are available for a tracked user", async () => {
// Process Alice keys, old crypto has a sleep(5ms) during the process
await jest.advanceTimersByTimeAsync(5);
await flushPromises();
// Alice is the local user and should be tracked !
const hasCrossSigningKeysForUser = await aliceClient.getCrypto()!.userHasCrossSigningKeys(TEST_USER_ID);
expect(hasCrossSigningKeysForUser).toBe(true);
});
it("Cross signing keys are not available for an unknown user", async () => {
const hasCrossSigningKeysForUser = await aliceClient.getCrypto()!.userHasCrossSigningKeys("@unknown:xyz");
expect(hasCrossSigningKeysForUser).toBe(false);
});
});
}); });

View File

@ -460,6 +460,23 @@ describe("RustCrypto", () => {
await expect(rustCrypto.userHasCrossSigningKeys()).resolves.toBe(true); await expect(rustCrypto.userHasCrossSigningKeys()).resolves.toBe(true);
}); });
it("returns true if the user is untracked, downloadUncached is set at true and the cross-signing keys are available", async () => {
fetchMock.post("path:/_matrix/client/v3/keys/query", {
device_keys: {
[testData.BOB_TEST_USER_ID]: {
[testData.BOB_TEST_DEVICE_ID]: testData.BOB_SIGNED_TEST_DEVICE_DATA,
},
},
...testData.BOB_SIGNED_CROSS_SIGNING_KEYS_DATA,
});
await expect(rustCrypto.userHasCrossSigningKeys(testData.BOB_TEST_USER_ID, true)).resolves.toBe(true);
});
it("returns false if the user is unknown", async () => {
await expect(rustCrypto.userHasCrossSigningKeys(testData.BOB_TEST_USER_ID)).resolves.toBe(false);
});
}); });
describe("createRecoveryKeyFromPassphrase", () => { describe("createRecoveryKeyFromPassphrase", () => {

View File

@ -2628,6 +2628,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @param userId - the user ID to get the cross-signing info for. * @param userId - the user ID to get the cross-signing info for.
* *
* @returns the cross signing information for the user. * @returns the cross signing information for the user.
* @deprecated Prefer {@link CryptoApi#userHasCrossSigningKeys}
*/ */
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null { public getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null {
if (!this.cryptoBackend) { if (!this.cryptoBackend) {

View File

@ -87,6 +87,7 @@ export interface CryptoBackend extends SyncCryptoCallbacks, CryptoApi {
* @param userId - the user ID to get the cross-signing info for. * @param userId - the user ID to get the cross-signing info for.
* *
* @returns the cross signing information for the user. * @returns the cross signing information for the user.
* @deprecated Prefer {@link CryptoApi#userHasCrossSigningKeys}
*/ */
getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null; getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null;

View File

@ -38,16 +38,6 @@ export interface CryptoApi {
*/ */
globalBlacklistUnverifiedDevices: boolean; globalBlacklistUnverifiedDevices: boolean;
/**
* Checks if the user has previously published cross-signing keys
*
* This means downloading the devicelist for the user and checking if the list includes
* the cross-signing pseudo-device.
*
* @returns true if the user has previously published cross-signing keys
*/
userHasCrossSigningKeys(): Promise<boolean>;
/** /**
* Perform any background tasks that can be done before a message is ready to * Perform any background tasks that can be done before a message is ready to
* send, in order to speed up sending of the message. * send, in order to speed up sending of the message.
@ -88,6 +78,22 @@ export interface CryptoApi {
*/ */
importRoomKeys(keys: IMegolmSessionData[], opts?: ImportRoomKeysOpts): Promise<void>; importRoomKeys(keys: IMegolmSessionData[], opts?: ImportRoomKeysOpts): Promise<void>;
/**
* Check if the given user has published cross-signing keys.
*
* - If the user is tracked, a `/keys/query` request is made to update locally the cross signing keys.
* - If the user is not tracked locally and downloadUncached is set to true,
* a `/keys/query` request is made to the server to retrieve the cross signing keys.
* - Otherwise, return false
*
* @param userId - the user ID to check. Defaults to the local user.
* @param downloadUncached - If true, download the device list for users whose device list we are not
* currently tracking. Defaults to false, in which case `false` will be returned for such users.
*
* @returns true if the cross signing keys are available.
*/
userHasCrossSigningKeys(userId?: string, downloadUncached?: boolean): Promise<boolean>;
/** /**
* Get the device information for the given list of users. * Get the device information for the given list of users.
* *

View File

@ -700,9 +700,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* *
* @internal * @internal
*/ */
public async userHasCrossSigningKeys(): Promise<boolean> { public async userHasCrossSigningKeys(userId = this.userId): Promise<boolean> {
await this.downloadKeys([this.userId]); await this.downloadKeys([userId]);
return this.deviceList.getStoredCrossSigningForUser(this.userId) !== null; return this.deviceList.getStoredCrossSigningForUser(userId) !== null;
} }
/** /**

View File

@ -244,20 +244,6 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
public globalBlacklistUnverifiedDevices = false; public globalBlacklistUnverifiedDevices = false;
/**
* Implementation of {@link CryptoApi.userHasCrossSigningKeys}.
*/
public async userHasCrossSigningKeys(): Promise<boolean> {
const userId = new RustSdkCryptoJs.UserId(this.userId);
/* make sure we have an *up-to-date* idea of the user's cross-signing keys. This is important, because if we
* return "false" here, we will end up generating new cross-signing keys and replacing the existing ones.
*/
const request = this.olmMachine.queryKeysForUsers([userId]);
await this.outgoingRequestProcessor.makeOutgoingRequest(request);
const userIdentity = await this.olmMachine.getIdentity(userId);
return userIdentity !== undefined;
}
public prepareToEncrypt(room: Room): void { public prepareToEncrypt(room: Room): void {
const encryptor = this.roomEncryptors[room.roomId]; const encryptor = this.roomEncryptors[room.roomId];
@ -289,6 +275,47 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
}); });
} }
/**
* Implementation of {@link CryptoApi.userHasCrossSigningKeys}.
*/
public async userHasCrossSigningKeys(userId = this.userId, downloadUncached = false): Promise<boolean> {
// TODO: could probably do with a more efficient way of doing this than returning the whole set and searching
const rustTrackedUsers: Set<RustSdkCryptoJs.UserId> = await this.olmMachine.trackedUsers();
let rustTrackedUser: RustSdkCryptoJs.UserId | undefined;
for (const u of rustTrackedUsers) {
if (userId === u.toString()) {
rustTrackedUser = u;
break;
}
}
if (rustTrackedUser !== undefined) {
if (userId === this.userId) {
/* make sure we have an *up-to-date* idea of the user's cross-signing keys. This is important, because if we
* return "false" here, we will end up generating new cross-signing keys and replacing the existing ones.
*/
const request = this.olmMachine.queryKeysForUsers([rustTrackedUser]);
await this.outgoingRequestProcessor.makeOutgoingRequest(request);
}
const userIdentity = await this.olmMachine.getIdentity(rustTrackedUser);
return userIdentity !== undefined;
} else if (downloadUncached) {
// Download the cross signing keys and check if the master key is available
const keyResult = await this.downloadDeviceList(new Set([userId]));
const keys = keyResult.master_keys?.[userId];
// No master key
if (!keys) return false;
// `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }
// We assume only a single key, and we want the bare form without type
// prefix, so we select the values.
return Boolean(Object.values(keys.keys)[0]);
} else {
return false;
}
}
/** /**
* Get the device information for the given list of users. * Get the device information for the given list of users.
* *