Skip to content

Commit

Permalink
WIP: Add support for key backups
Browse files Browse the repository at this point in the history
Unusable until the rust-sdk has Node bindings for key backups
  • Loading branch information
AndrewFerr committed Jul 7, 2023
1 parent bb93184 commit 74f8864
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 1 deletion.
75 changes: 75 additions & 0 deletions src/MatrixClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import { DMs } from "./DMs";
import { ServerVersions } from "./models/ServerVersions";
import { RoomCreateOptions } from "./models/CreateRoom";
import { PresenceState } from './models/events/PresenceEvent';
import { IKeyBackupInfo, IKeyBackupInfoRetrieved, IKeyBackupInfoUpdate, IKeyBackupVersion, KeyBackupVersion } from "./models/KeyBackup";
import { MatrixError } from "./models/MatrixError";

const SYNC_BACKOFF_MIN_MS = 5000;
const SYNC_BACKOFF_MAX_MS = 15000;
Expand Down Expand Up @@ -1962,6 +1964,79 @@ export class MatrixClient extends EventEmitter {
});
}

/**
* Get information about the latest room key backup version.
* @returns {Promise<IKeyBackupInfoRetrieved|null>} Resolves to the retrieved key backup info,
* or null if there is no existing backup.
*/
public async getKeyBackupVersion(): Promise<IKeyBackupInfoRetrieved|null> {
try {
return await this.doRequest("GET", "/_matrix/client/v3/room_keys/version");
} catch (e) {
if (e instanceof MatrixError && e.errcode === "M_NOT_FOUND") {
return null;
} else {
throw e;
}
}
}

/**
* Create a new room key backup.
* @param {IKeyBackupInfo} info The properties of the key backup to create.
* @returns {Promise<IKeyBackupVersion>} Resolves to the version id of the new backup.
*/
public async createKeyBackupVersion(info: IKeyBackupInfo): Promise<IKeyBackupVersion> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}

const data = {
...info,
signatures: this.crypto.sign(info),
};
return await this.doRequest("POST", "/_matrix/client/v3/room_keys/version", null, data);
}

/**
* Update an existing room key backup.
* @param {KeyBackupVersion} version The key backup version to update.
* @param {IKeyBackupInfoUpdate} info The properties of the key backup to be applied.
* @returns {Promise<void>} Resolves when complete.
*/
public async updateKeyBackupVersion(version: KeyBackupVersion, info: IKeyBackupInfoUpdate): Promise<void> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}

const data = {
...info,
signatures: this.crypto.sign(info),
};
await this.doRequest("PUT", `/_matrix/client/v3/room_keys/version/${version}`, null, data);
}

/**
* Enable backing up of room keys.
* @param {IKeyBackupInfoRetrieved} info The configuration for key backup behaviour,
* as returned by {@link getKeyBackupVersion}.
* @returns {Promise<void>} Resolves when complete.
*/
public async enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise<void> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}

this.crypto.enableKeyBackup(info);
}

/**
* Disable backing up of room keys.
*/
public disableKeyBackup(): void {
this.crypto?.disableKeyBackup();
}

/**
* Get relations for a given event.
* @param {string} roomId The room ID to for the given event.
Expand Down
18 changes: 18 additions & 0 deletions src/e2ee/CryptoClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { EncryptedFile } from "../models/events/MessageEvent";
import { RustSdkCryptoStorageProvider } from "../storage/RustSdkCryptoStorageProvider";
import { RustEngine, SYNC_LOCK_NAME } from "./RustEngine";
import { MembershipEvent } from "../models/events/MembershipEvent";
import { IKeyBackupInfoRetrieved } from "../models/KeyBackup";

/**
* Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly
Expand Down Expand Up @@ -284,4 +285,21 @@ export class CryptoClient {
const decrypted = Attachment.decrypt(encrypted);
return Buffer.from(decrypted);
}

/**
* Enable backing up of room keys.
* @param {IKeyBackupInfoRetrieved} info The configuration for key backup behaviour,
* as returned by {@link MatrixClient#getKeyBackupVersion}.
* @returns {Promise<void>} Resolves when complete.
*/
public async enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise<void> {
await this.engine.enableKeyBackup(info);
}

/**
* Disable backing up of room keys.
*/
public disableKeyBackup(): void {
this.engine.disableKeyBackup();
}
}
31 changes: 30 additions & 1 deletion src/e2ee/RustEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import {
KeysUploadRequest,
KeysQueryRequest,
ToDeviceRequest,
KeysBackupRequest,
} from "@matrix-org/matrix-sdk-crypto-nodejs";
import * as AsyncLock from "async-lock";

import { MatrixClient } from "../MatrixClient";
import { LogService } from "../logging/LogService";
import { ICryptoRoomInformation } from "./ICryptoRoomInformation";
import { EncryptionAlgorithm } from "../models/Crypto";
import { EncryptionEvent } from "../models/events/EncryptionEvent";
import { IKeyBackupInfoRetrieved, KeyBackupVersion } from "../models/KeyBackup";

/**
* @internal
Expand All @@ -29,6 +32,9 @@ export const SYNC_LOCK_NAME = "sync";
export class RustEngine {
public readonly lock = new AsyncLock();

private keyBackupVersion: KeyBackupVersion|undefined;
// TODO: keyBackupPromise that can be awaited by others

public constructor(public readonly machine: OlmMachine, private client: MatrixClient) {
}

Expand Down Expand Up @@ -59,7 +65,8 @@ export class RustEngine {
case RequestType.SignatureUpload:
throw new Error("Bindings error: Backup feature not possible");
case RequestType.KeysBackup:
throw new Error("Bindings error: Backup feature not possible");
await this.processKeysBackupRequest(request);
break;
default:
throw new Error("Bindings error: Unrecognized request type: " + request.type);
}
Expand Down Expand Up @@ -128,6 +135,17 @@ export class RustEngine {
});
}

public async enableKeyBackup(info: IKeyBackupInfoRetrieved) {
LogService.warn("RustEngine", "**STUB** Server-side key backups not yet implemented!");
this.keyBackupVersion = info.version;
// await this.machine.enableBackupV1(info);
}

public async disableKeyBackup() {
this.keyBackupVersion = undefined;
// await this.machine.disableBackup();
}

private async processKeysClaimRequest(request: KeysClaimRequest) {
const resp = await this.client.doRequest("POST", "/_matrix/client/v3/keys/claim", null, JSON.parse(request.body));
await this.machine.markRequestAsSent(request.id, request.type, JSON.stringify(resp));
Expand All @@ -154,4 +172,15 @@ export class RustEngine {
const resp = await this.client.sendToDevices(type, messages);
await this.machine.markRequestAsSent(id, RequestType.ToDevice, JSON.stringify(resp));
}

private async processKeysBackupRequest(request: KeysBackupRequest) {
let resp: Awaited<ReturnType<MatrixClient["doRequest"]>>;
try {
resp = await this.client.doRequest("PUT", "/_matrix/client/v3/room_keys/keys", { version: this.keyBackupVersion }, JSON.parse(request.body));
} catch (e) {
this.client.emit("crypto.failed_backup", e);
return;
}
await this.machine.markRequestAsSent(request.id, request.type, JSON.stringify(resp));
}
}
36 changes: 36 additions & 0 deletions src/models/KeyBackup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Signatures } from "./Crypto";

/**
* The kinds of key backup encryption algorithms allowed by the spec.
* @category Models
*/
export enum KeyBackupEncryptionAlgorithm {
MegolmBackupV1Curve25519AesSha2 = "m.megolm_backup.v1.curve25519-aes-sha2",
}

/**
* Information about a server-side key backup.
*/
export interface IKeyBackupInfo {
algorithm: string | KeyBackupEncryptionAlgorithm;
auth_data: object;
}

export type KeyBackupVersion = string;

export interface IKeyBackupVersion {
version: KeyBackupVersion;
}

export interface IKeyBackupInfoRetrieved extends IKeyBackupInfo, IKeyBackupVersion {
count: number;
etag: string;
}

export type IKeyBackupInfoUpdate = IKeyBackupInfo & Partial<IKeyBackupVersion>;

export interface ICurve25519AuthDataUnsigned {
public_key: string;
}

export type ICurve25519AuthData = ICurve25519AuthDataUnsigned & Signatures;

0 comments on commit 74f8864

Please sign in to comment.