Skip to content

Commit

Permalink
Add getUserDeviceInfo WIP impl to rust-crypto
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros committed Apr 13, 2023
1 parent 58285ab commit 91e1502
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/crypto/deviceinfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface IDevice {
// user-Id → device-Id → IDevice
export type DeviceMap = Map<string, Map<string, IDevice>>;

enum DeviceVerification {
export enum DeviceVerification {
Blocked = -1,
Unverified = 0,
Verified = 1,
Expand Down
74 changes: 74 additions & 0 deletions src/rust-crypto/device-convertor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";

import { DeviceVerification, IDevice } from "../crypto/deviceinfo";
import { DeviceKeys } from "../client";

/**
* Convert a {@link RustSdkCryptoJs.Device} to a {@link IDevice}
* @param device - Rust Sdk device
*/
export function rustDeviceToJsDevice(device: RustSdkCryptoJs.Device): IDevice {
const keys: Record<string, string> = Object.create(null);
for (const [keyId, key] of device.keys.entries()) {
keys[keyId.toString()] = key.toBase64();
}

let verified: DeviceVerification = DeviceVerification.Unverified;
if (device.isBlacklisted()) {
verified = DeviceVerification.Blocked;
} else if (device.isVerified()) {
verified = DeviceVerification.Verified;
}

return {
algorithms: [], // TODO
keys: keys,
known: false, // TODO
signatures: undefined, // TODO
verified,
};
}

/**
* Convert {@link DeviceKeys} from `/keys/query` request to a `Map<string, IDevice>`
* @param deviceKeys - Device keys object to convert
*/
export function deviceKeysToIDeviceMap(deviceKeys: DeviceKeys): Map<string, IDevice> {
return new Map(
Object.entries(deviceKeys).map(([deviceId, device]) => [deviceId, downloadDeviceToJsDevice(device)]),
);
}

// Device from `/keys/query` request
type QueryDevice = DeviceKeys[keyof DeviceKeys];

/**
* Convert `/keys/query` {@link QueryDevice} device to {@link IDevice}
* @param device - Device from `/keys/query` request
*/
export function downloadDeviceToJsDevice(device: QueryDevice): IDevice {
return {
algorithms: device.algorithms,
keys: device.keys,
known: false,
signatures: device.signatures,
verified: DeviceVerification.Unverified,
unsigned: device.unsigned,
};
}
56 changes: 52 additions & 4 deletions src/rust-crypto/rust-crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ import { Room } from "../models/room";
import { RoomMember } from "../models/room-member";
import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
import { logger } from "../logger";
import { IHttpOpts, MatrixHttpApi } from "../http-api";
import { IHttpOpts, MatrixHttpApi, Method } from "../http-api";
import { DeviceTrustLevel, UserTrustLevel } from "../crypto/CrossSigning";
import { RoomEncryptor } from "./RoomEncryptor";
import { OutgoingRequest, OutgoingRequestProcessor } from "./OutgoingRequestProcessor";
import { KeyClaimManager } from "./KeyClaimManager";
import { MapWithDefault } from "../utils";
import { DeviceMap } from "../crypto/deviceinfo";
import { DeviceMap, IDevice } from "../crypto/deviceinfo";
import { deviceKeysToIDeviceMap, rustDeviceToJsDevice } from "./device-convertor";
import { IDownloadKeyResult, IQueryKeysRequest } from "../client";

/**
* An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto.
Expand All @@ -53,7 +55,7 @@ export class RustCrypto implements CryptoBackend {

public constructor(
private readonly olmMachine: RustSdkCryptoJs.OlmMachine,
http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
private readonly http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
_userId: string,
_deviceId: string,
) {
Expand Down Expand Up @@ -176,7 +178,53 @@ export class RustCrypto implements CryptoBackend {
* @returns A map `{@link DeviceMap}`.
*/
public async getUserDeviceInfo(userIds: string[], downloadUncached = false): Promise<DeviceMap> {
return new Map();
const iDeviceMapByUserId = new Map<string, Map<string, IDevice>>();
const trackedUsers: Set<RustSdkCryptoJs.UserId> = await this.olmMachine.trackedUsers();

// Keep untracked user to download their keys after
const untrackedUsers: Set<string> = new Set();

for (const userId of userIds) {
const rustUserId = new RustSdkCryptoJs.UserId(userId);

// if this is a tracked user, we can just fetch the device list from the rust-sdk
// (NB: this is probably ok even if we race with a leave event such that we stop tracking the user's
// devices: the rust-sdk will return the last-known device list, which will be good enough.)
if (trackedUsers.has(rustUserId)) {
iDeviceMapByUserId.set(userId, await this.getUserDevices(rustUserId));
} else {
untrackedUsers.add(userId);
}
}

// for any users whose device lists we are not tracking, fall back to downloading the device list
// over HTTP.
if (untrackedUsers.size >= 1) {
const queryResult = await this.downloadDeviceList(untrackedUsers);
Object.entries(queryResult.device_keys).forEach(([userId, deviceKeys]) =>
iDeviceMapByUserId.set(userId, deviceKeysToIDeviceMap(deviceKeys)),
);
}

return iDeviceMapByUserId;
}

private async getUserDevices(rustUserId: RustSdkCryptoJs.UserId): Promise<Map<string, IDevice>> {
const devices: RustSdkCryptoJs.UserDevices = await this.olmMachine.getUserDevices(rustUserId);
return new Map(
devices
.devices()
.map((device: RustSdkCryptoJs.Device) => [device.deviceId.toString(), rustDeviceToJsDevice(device)]),
);
}

private async downloadDeviceList(untrackedUsers: Set<string>): Promise<IDownloadKeyResult> {
const queryBody: IQueryKeysRequest = { device_keys: {} };
untrackedUsers.forEach((user) => (queryBody.device_keys[user] = []));

return await this.http.authedRequest(Method.Post, "/_matrix/client/v3/keys/query", undefined, queryBody, {
prefix: "",
});
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down

0 comments on commit 91e1502

Please sign in to comment.