diff --git a/src/client.ts b/src/client.ts index 32874e2e1..b89594ab5 100644 --- a/src/client.ts +++ b/src/client.ts @@ -47,7 +47,7 @@ import { PREFIX_UNSTABLE, retryNetworkOperation, } from "./http-api"; -import { Crypto, fixBackupKey, IBootstrapCrossSigningOpts, isCryptoAvailable } from './crypto'; +import { Crypto, fixBackupKey, IBootstrapCrossSigningOpts, IMegolmSessionData, isCryptoAvailable } from './crypto'; import { DeviceInfo, IDevice } from "./crypto/deviceinfo"; import { decodeRecoveryKey } from './crypto/recoverykey'; import { keyFromAuthData } from './crypto/key_passphrase'; @@ -2096,7 +2096,7 @@ export class MatrixClient extends EventEmitter { * @return {Promise} a promise which resolves when the keys * have been imported */ - public importRoomKeys(keys: any[], opts: IImportRoomKeysOpts): Promise { + public importRoomKeys(keys: IMegolmSessionData[], opts: IImportRoomKeysOpts): Promise { if (!this.crypto) { throw new Error("End-to-end encryption disabled"); } diff --git a/src/crypto/algorithms/base.js b/src/crypto/algorithms/base.ts similarity index 63% rename from src/crypto/algorithms/base.js rename to src/crypto/algorithms/base.ts index 87b8a82c0..256a499de 100644 --- a/src/crypto/algorithms/base.js +++ b/src/crypto/algorithms/base.ts @@ -1,5 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd +Copyright 2016 - 2021 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. @@ -20,13 +20,20 @@ limitations under the License. * @module */ +import { MatrixClient } from "../../client"; +import { Room } from "../../models/room"; +import { OlmDevice } from "../OlmDevice"; +import { MatrixEvent, RoomMember } from "../.."; +import { IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from ".."; +import { DeviceInfo } from "../deviceinfo"; + /** * map of registered encryption algorithm classes. A map from string to {@link * module:crypto/algorithms/base.EncryptionAlgorithm|EncryptionAlgorithm} class * * @type {Object.} */ -export const ENCRYPTION_CLASSES = {}; +export const ENCRYPTION_CLASSES: Record = {}; /** * map of registered encryption algorithm classes. Map from string to {@link @@ -34,7 +41,16 @@ export const ENCRYPTION_CLASSES = {}; * * @type {Object.} */ -export const DECRYPTION_CLASSES = {}; +export const DECRYPTION_CLASSES: Record = {}; + +interface IParams { + userId: string; + deviceId: string; + crypto: Crypto; + olmDevice: OlmDevice; + baseApis: MatrixClient; + roomId: string; +} /** * base type for encryption implementations @@ -50,14 +66,21 @@ export const DECRYPTION_CLASSES = {}; * @param {string} params.roomId The ID of the room we will be sending to * @param {object} params.config The body of the m.room.encryption event */ -export class EncryptionAlgorithm { - constructor(params) { - this._userId = params.userId; - this._deviceId = params.deviceId; - this._crypto = params.crypto; - this._olmDevice = params.olmDevice; - this._baseApis = params.baseApis; - this._roomId = params.roomId; +export abstract class EncryptionAlgorithm { + protected readonly userId: string; + protected readonly deviceId: string; + protected readonly crypto: Crypto; + protected readonly olmDevice: OlmDevice; + protected readonly baseApis: MatrixClient; + protected readonly roomId: string; + + constructor(params: IParams) { + this.userId = params.userId; + this.deviceId = params.deviceId; + this.crypto = params.crypto; + this.olmDevice = params.olmDevice; + this.baseApis = params.baseApis; + this.roomId = params.roomId; } /** @@ -66,21 +89,22 @@ export class EncryptionAlgorithm { * * @param {module:models/room} room the room the event is in */ - prepareToEncrypt(room) { - } + public abstract prepareToEncrypt(room: Room): void; /** * Encrypt a message event * * @method module:crypto/algorithms/base.EncryptionAlgorithm.encryptMessage + * @public * @abstract * * @param {module:models/room} room * @param {string} eventType - * @param {object} plaintext event content + * @param {object} content event content * * @return {Promise} Promise which resolves to the new event body */ + public abstract encryptMessage(room: Room, eventType: string, content: object): Promise; /** * Called when the membership of a member of the room changes. @@ -89,9 +113,18 @@ export class EncryptionAlgorithm { * @param {module:models/room-member} member user whose membership changed * @param {string=} oldMembership previous membership * @public + * @abstract */ - onRoomMembership(event, member, oldMembership) { - } + public abstract onRoomMembership(event: MatrixEvent, member: RoomMember, oldMembership?: string); + + public abstract reshareKeyWithDevice( + senderKey: string, + sessionId: string, + userId: string, + device: DeviceInfo, + ): Promise; + + public forceDiscardSession?: () => void; } /** @@ -106,13 +139,19 @@ export class EncryptionAlgorithm { * @param {string=} params.roomId The ID of the room we will be receiving * from. Null for to-device events. */ -export class DecryptionAlgorithm { - constructor(params) { - this._userId = params.userId; - this._crypto = params.crypto; - this._olmDevice = params.olmDevice; - this._baseApis = params.baseApis; - this._roomId = params.roomId; +export abstract class DecryptionAlgorithm { + private readonly userId: string; + private readonly crypto: Crypto; + private readonly olmDevice: OlmDevice; + private readonly baseApis: MatrixClient; + private readonly roomId: string; + + constructor(params: Omit) { + this.userId = params.userId; + this.crypto = params.crypto; + this.olmDevice = params.olmDevice; + this.baseApis = params.baseApis; + this.roomId = params.roomId; } /** @@ -127,6 +166,7 @@ export class DecryptionAlgorithm { * resolves once we have finished decrypting. Rejects with an * `algorithms.DecryptionError` if there is a problem decrypting the event. */ + public abstract decryptEvent(event: MatrixEvent): Promise; /** * Handle a key event @@ -135,7 +175,7 @@ export class DecryptionAlgorithm { * * @param {module:models/event.MatrixEvent} params event key event */ - onRoomKeyEvent(params) { + public onRoomKeyEvent(params: MatrixEvent): void { // ignore by default } @@ -143,8 +183,9 @@ export class DecryptionAlgorithm { * Import a room key * * @param {module:crypto/OlmDevice.MegolmSessionData} session + * @param {object} opts object */ - importRoomKey(session) { + public async importRoomKey(session: IMegolmSessionData, opts: object): Promise { // ignore by default } @@ -155,7 +196,7 @@ export class DecryptionAlgorithm { * @return {Promise} true if we have the keys and could (theoretically) share * them; else false. */ - hasKeysForKeyRequest(keyRequest) { + public hasKeysForKeyRequest(keyRequest: IncomingRoomKeyRequest): Promise { return Promise.resolve(false); } @@ -164,7 +205,7 @@ export class DecryptionAlgorithm { * * @param {module:crypto~IncomingRoomKeyRequest} keyRequest */ - shareKeysWithDevice(keyRequest) { + public shareKeysWithDevice(keyRequest: IncomingRoomKeyRequest) { throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm"); } @@ -174,9 +215,12 @@ export class DecryptionAlgorithm { * * @param {string} senderKey the sender's key */ - async retryDecryptionFromSender(senderKey) { + public async retryDecryptionFromSender(senderKey: string): Promise { // ignore by default } + + public onRoomKeyWithheldEvent?: (event: MatrixEvent) => Promise; + public sendSharedHistoryInboundSessions?: (devicesByUser: Record) => Promise; } /** @@ -191,22 +235,21 @@ export class DecryptionAlgorithm { * @extends Error */ export class DecryptionError extends Error { - constructor(code, msg, details) { + public readonly detailedString: string; + + constructor(public readonly code: string, msg: string, details: Record) { super(msg); this.code = code; this.name = 'DecryptionError'; - this.detailedString = _detailedStringForDecryptionError(this, details); + this.detailedString = detailedStringForDecryptionError(this, details); } } -function _detailedStringForDecryptionError(err, details) { +function detailedStringForDecryptionError(err: DecryptionError, details: Record): string { let result = err.name + '[msg: ' + err.message; if (details) { - result += ', ' + - Object.keys(details).map( - (k) => k + ': ' + details[k], - ).join(', '); + result += ', ' + Object.keys(details).map((k) => k + ': ' + details[k]).join(', '); } result += ']'; @@ -224,7 +267,7 @@ function _detailedStringForDecryptionError(err, details) { * @extends Error */ export class UnknownDeviceError extends Error { - constructor(msg, devices) { + constructor(msg: string, public readonly devices: Record>) { super(msg); this.name = "UnknownDeviceError"; this.devices = devices; @@ -244,7 +287,11 @@ export class UnknownDeviceError extends Error { * module:crypto/algorithms/base.DecryptionAlgorithm|DecryptionAlgorithm} * implementation */ -export function registerAlgorithm(algorithm, encryptor, decryptor) { +export function registerAlgorithm( + algorithm: string, + encryptor: EncryptionAlgorithm, + decryptor: DecryptionAlgorithm, +): void { ENCRYPTION_CLASSES[algorithm] = encryptor; DECRYPTION_CLASSES[algorithm] = decryptor; } diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 9d60276e3..132821219 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -117,13 +117,14 @@ export interface IRoomKeyRequestBody extends IRoomKey { sender_key: string } -interface IMegolmSessionData { +export interface IMegolmSessionData { sender_key: string; forwarding_curve25519_key_chain: string[]; sender_claimed_keys: Record; room_id: string; session_id: string; session_key: string; + algorithm: string; } /* eslint-enable camelcase */ @@ -168,7 +169,7 @@ interface ISignableObject { unsigned?: object } -interface IEventDecryptionResult { +export interface IEventDecryptionResult { clearEvent: object; senderCurve25519Key?: string; claimedEd25519Key?: string; @@ -193,7 +194,7 @@ export class Crypto extends EventEmitter { private readonly reEmitter: ReEmitter; private readonly verificationMethods: any; // TODO types - private readonly supportedAlgorithms: DecryptionAlgorithm[]; + private readonly supportedAlgorithms: string[]; private readonly outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager; private readonly toDeviceVerificationRequests: ToDeviceRequests; private readonly inRoomVerificationRequests: InRoomRequests; @@ -2630,7 +2631,7 @@ export class Crypto extends EventEmitter { * @param {Function} opts.progressCallback called with an object which has a stage param * @return {Promise} a promise which resolves once the keys have been imported */ - public importRoomKeys(keys: IRoomKey[], opts: any = {}): Promise { // TODO types + public importRoomKeys(keys: IMegolmSessionData[], opts: any = {}): Promise { // TODO types let successes = 0; let failures = 0; const total = keys.length; @@ -3430,9 +3431,7 @@ export class Crypto extends EventEmitter { } try { - await encryptor.reshareKeyWithDevice( - body.sender_key, body.session_id, userId, device, - ); + await encryptor.reshareKeyWithDevice(body.sender_key, body.session_id, userId, device); } catch (e) { logger.warn( "Failed to re-share keys for session " + body.session_id + @@ -3643,7 +3642,7 @@ export function fixBackupKey(key: string): string | null { * the relevant crypto algorithm implementation to share the keys for * this request. */ -class IncomingRoomKeyRequest { +export class IncomingRoomKeyRequest { public readonly userId: string; public readonly deviceId: string; public readonly requestId: string;