You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-25 05:23:13 +03:00
Type the rest of algorithms because tests are unhappy otherwise
This commit is contained in:
@@ -59,7 +59,7 @@ enum TrackingStatus {
|
|||||||
UpToDate,
|
UpToDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceInfoMap = Record<string, Record<string, IDevice>>;
|
export type DeviceInfoMap = Record<string, Record<string, DeviceInfo>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @alias module:crypto/DeviceList
|
* @alias module:crypto/DeviceList
|
||||||
@@ -70,7 +70,7 @@ export class DeviceList extends EventEmitter {
|
|||||||
// [device info]
|
// [device info]
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
private devices: DeviceInfoMap = {};
|
private devices: Record<string, Record<string, IDevice>> = {};
|
||||||
|
|
||||||
// userId -> {
|
// userId -> {
|
||||||
// [key info]
|
// [key info]
|
||||||
@@ -315,7 +315,7 @@ export class DeviceList extends EventEmitter {
|
|||||||
* @return {Object} userId->deviceId->{@link module:crypto/deviceinfo|DeviceInfo}.
|
* @return {Object} userId->deviceId->{@link module:crypto/deviceinfo|DeviceInfo}.
|
||||||
*/
|
*/
|
||||||
private getDevicesFromStore(userIds: string[]): DeviceInfoMap {
|
private getDevicesFromStore(userIds: string[]): DeviceInfoMap {
|
||||||
const stored = {};
|
const stored: DeviceInfoMap = {};
|
||||||
userIds.map((u) => {
|
userIds.map((u) => {
|
||||||
stored[u] = {};
|
stored[u] = {};
|
||||||
const devices = this.getStoredDevicesForUser(u) || [];
|
const devices = this.getStoredDevicesForUser(u) || [];
|
||||||
@@ -463,27 +463,11 @@ export class DeviceList extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Replaces the list of devices for a user with the given device list
|
* Replaces the list of devices for a user with the given device list
|
||||||
*
|
*
|
||||||
* @param {string} u The user ID
|
* @param {string} userId The user ID
|
||||||
* @param {Object} devs New device info for user
|
* @param {Object} devices New device info for user
|
||||||
*/
|
*/
|
||||||
public storeDevicesForUser(u: string, devs: Record<string, IDevice>): void {
|
public storeDevicesForUser(userId: string, devices: Record<string, IDevice>): void {
|
||||||
// remove previous devices from userByIdentityKey
|
this.setRawStoredDevicesForUser(userId, devices);
|
||||||
if (this.devices[u] !== undefined) {
|
|
||||||
for (const [deviceId, dev] of Object.entries(this.devices[u])) {
|
|
||||||
const identityKey = dev.keys['curve25519:'+deviceId];
|
|
||||||
|
|
||||||
delete this.userByIdentityKey[identityKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.devices[u] = devs;
|
|
||||||
|
|
||||||
// add new ones
|
|
||||||
for (const [deviceId, dev] of Object.entries(devs)) {
|
|
||||||
const identityKey = dev.keys['curve25519:'+deviceId];
|
|
||||||
|
|
||||||
this.userByIdentityKey[identityKey] = u;
|
|
||||||
}
|
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -859,7 +843,7 @@ class DeviceListUpdateSerialiser {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// put the updates into the object that will be returned as our results
|
// put the updates into the object that will be returned as our results
|
||||||
const storage = {};
|
const storage: Record<string, IDevice> = {};
|
||||||
Object.keys(userStore).forEach((deviceId) => {
|
Object.keys(userStore).forEach((deviceId) => {
|
||||||
storage[deviceId] = userStore[deviceId].toStorage();
|
storage[deviceId] = userStore[deviceId].toStorage();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { MatrixClient } from "../../client";
|
|||||||
import { Room } from "../../models/room";
|
import { Room } from "../../models/room";
|
||||||
import { OlmDevice } from "../OlmDevice";
|
import { OlmDevice } from "../OlmDevice";
|
||||||
import { MatrixEvent, RoomMember } from "../..";
|
import { MatrixEvent, RoomMember } from "../..";
|
||||||
import { IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "..";
|
import { Crypto, IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "..";
|
||||||
import { DeviceInfo } from "../deviceinfo";
|
import { DeviceInfo } from "../deviceinfo";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,7 +33,9 @@ import { DeviceInfo } from "../deviceinfo";
|
|||||||
*
|
*
|
||||||
* @type {Object.<string, function(new: module:crypto/algorithms/base.EncryptionAlgorithm)>}
|
* @type {Object.<string, function(new: module:crypto/algorithms/base.EncryptionAlgorithm)>}
|
||||||
*/
|
*/
|
||||||
export const ENCRYPTION_CLASSES: Record<string, EncryptionAlgorithm> = {};
|
export const ENCRYPTION_CLASSES: Record<string, new (params: IParams) => EncryptionAlgorithm> = {};
|
||||||
|
|
||||||
|
type DecryptionClassParams = Omit<IParams, "deviceId" | "config">;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* map of registered encryption algorithm classes. Map from string to {@link
|
* map of registered encryption algorithm classes. Map from string to {@link
|
||||||
@@ -41,7 +43,7 @@ export const ENCRYPTION_CLASSES: Record<string, EncryptionAlgorithm> = {};
|
|||||||
*
|
*
|
||||||
* @type {Object.<string, function(new: module:crypto/algorithms/base.DecryptionAlgorithm)>}
|
* @type {Object.<string, function(new: module:crypto/algorithms/base.DecryptionAlgorithm)>}
|
||||||
*/
|
*/
|
||||||
export const DECRYPTION_CLASSES: Record<string, DecryptionAlgorithm> = {};
|
export const DECRYPTION_CLASSES: Record<string, new (params: DecryptionClassParams) => DecryptionAlgorithm> = {};
|
||||||
|
|
||||||
interface IParams {
|
interface IParams {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -50,6 +52,7 @@ interface IParams {
|
|||||||
olmDevice: OlmDevice;
|
olmDevice: OlmDevice;
|
||||||
baseApis: MatrixClient;
|
baseApis: MatrixClient;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
|
config: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +92,7 @@ export abstract class EncryptionAlgorithm {
|
|||||||
*
|
*
|
||||||
* @param {module:models/room} room the room the event is in
|
* @param {module:models/room} room the room the event is in
|
||||||
*/
|
*/
|
||||||
public abstract prepareToEncrypt(room: Room): void;
|
public prepareToEncrypt(room: Room): void {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt a message event
|
* Encrypt a message event
|
||||||
@@ -115,16 +118,16 @@ export abstract class EncryptionAlgorithm {
|
|||||||
* @public
|
* @public
|
||||||
* @abstract
|
* @abstract
|
||||||
*/
|
*/
|
||||||
public abstract onRoomMembership(event: MatrixEvent, member: RoomMember, oldMembership?: string);
|
public onRoomMembership(event: MatrixEvent, member: RoomMember, oldMembership?: string): void {}
|
||||||
|
|
||||||
public abstract reshareKeyWithDevice(
|
public reshareKeyWithDevice?(
|
||||||
senderKey: string,
|
senderKey: string,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
device: DeviceInfo,
|
device: DeviceInfo,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
public forceDiscardSession?: () => void;
|
public forceDiscardSession?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,13 +143,13 @@ export abstract class EncryptionAlgorithm {
|
|||||||
* from. Null for to-device events.
|
* from. Null for to-device events.
|
||||||
*/
|
*/
|
||||||
export abstract class DecryptionAlgorithm {
|
export abstract class DecryptionAlgorithm {
|
||||||
private readonly userId: string;
|
protected readonly userId: string;
|
||||||
private readonly crypto: Crypto;
|
protected readonly crypto: Crypto;
|
||||||
private readonly olmDevice: OlmDevice;
|
protected readonly olmDevice: OlmDevice;
|
||||||
private readonly baseApis: MatrixClient;
|
protected readonly baseApis: MatrixClient;
|
||||||
private readonly roomId: string;
|
protected readonly roomId: string;
|
||||||
|
|
||||||
constructor(params: Omit<IParams, "deviceId">) {
|
constructor(params: DecryptionClassParams) {
|
||||||
this.userId = params.userId;
|
this.userId = params.userId;
|
||||||
this.crypto = params.crypto;
|
this.crypto = params.crypto;
|
||||||
this.olmDevice = params.olmDevice;
|
this.olmDevice = params.olmDevice;
|
||||||
@@ -205,7 +208,7 @@ export abstract class DecryptionAlgorithm {
|
|||||||
*
|
*
|
||||||
* @param {module:crypto~IncomingRoomKeyRequest} keyRequest
|
* @param {module:crypto~IncomingRoomKeyRequest} keyRequest
|
||||||
*/
|
*/
|
||||||
public shareKeysWithDevice(keyRequest: IncomingRoomKeyRequest) {
|
public shareKeysWithDevice(keyRequest: IncomingRoomKeyRequest): void {
|
||||||
throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm");
|
throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,12 +218,13 @@ export abstract class DecryptionAlgorithm {
|
|||||||
*
|
*
|
||||||
* @param {string} senderKey the sender's key
|
* @param {string} senderKey the sender's key
|
||||||
*/
|
*/
|
||||||
public async retryDecryptionFromSender(senderKey: string): Promise<void> {
|
public async retryDecryptionFromSender(senderKey: string): Promise<boolean> {
|
||||||
// ignore by default
|
// ignore by default
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRoomKeyWithheldEvent?: (event: MatrixEvent) => Promise<void>;
|
public onRoomKeyWithheldEvent?(event: MatrixEvent): Promise<void>;
|
||||||
public sendSharedHistoryInboundSessions?: (devicesByUser: Record<string, DeviceInfo[]>) => Promise<void>;
|
public sendSharedHistoryInboundSessions?(devicesByUser: Record<string, DeviceInfo[]>): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -237,7 +241,7 @@ export abstract class DecryptionAlgorithm {
|
|||||||
export class DecryptionError extends Error {
|
export class DecryptionError extends Error {
|
||||||
public readonly detailedString: string;
|
public readonly detailedString: string;
|
||||||
|
|
||||||
constructor(public readonly code: string, msg: string, details: Record<string, string>) {
|
constructor(public readonly code: string, msg: string, details?: Record<string, string>) {
|
||||||
super(msg);
|
super(msg);
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.name = 'DecryptionError';
|
this.name = 'DecryptionError';
|
||||||
@@ -245,7 +249,7 @@ export class DecryptionError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function detailedStringForDecryptionError(err: DecryptionError, details: Record<string, string>): string {
|
function detailedStringForDecryptionError(err: DecryptionError, details?: Record<string, string>): string {
|
||||||
let result = err.name + '[msg: ' + err.message;
|
let result = err.name + '[msg: ' + err.message;
|
||||||
|
|
||||||
if (details) {
|
if (details) {
|
||||||
@@ -289,8 +293,8 @@ export class UnknownDeviceError extends Error {
|
|||||||
*/
|
*/
|
||||||
export function registerAlgorithm(
|
export function registerAlgorithm(
|
||||||
algorithm: string,
|
algorithm: string,
|
||||||
encryptor: EncryptionAlgorithm,
|
encryptor: new (params: IParams) => EncryptionAlgorithm,
|
||||||
decryptor: DecryptionAlgorithm,
|
decryptor: new (params: Omit<IParams, "deviceId">) => DecryptionAlgorithm,
|
||||||
): void {
|
): void {
|
||||||
ENCRYPTION_CLASSES[algorithm] = encryptor;
|
ENCRYPTION_CLASSES[algorithm] = encryptor;
|
||||||
DECRYPTION_CLASSES[algorithm] = decryptor;
|
DECRYPTION_CLASSES[algorithm] = decryptor;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
File diff suppressed because it is too large
Load Diff
1833
src/crypto/algorithms/megolm.ts
Normal file
1833
src/crypto/algorithms/megolm.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,361 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 OpenMarket Ltd
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines m.olm encryption/decryption
|
|
||||||
*
|
|
||||||
* @module crypto/algorithms/olm
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { logger } from '../../logger';
|
|
||||||
import * as utils from "../../utils";
|
|
||||||
import { polyfillSuper } from "../../utils";
|
|
||||||
import * as olmlib from "../olmlib";
|
|
||||||
import { DeviceInfo } from "../deviceinfo";
|
|
||||||
import {
|
|
||||||
DecryptionAlgorithm,
|
|
||||||
DecryptionError,
|
|
||||||
EncryptionAlgorithm,
|
|
||||||
registerAlgorithm,
|
|
||||||
} from "./base";
|
|
||||||
|
|
||||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Olm encryption implementation
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @extends {module:crypto/algorithms/EncryptionAlgorithm}
|
|
||||||
*
|
|
||||||
* @param {object} params parameters, as per
|
|
||||||
* {@link module:crypto/algorithms/EncryptionAlgorithm}
|
|
||||||
*/
|
|
||||||
function OlmEncryption(params) {
|
|
||||||
polyfillSuper(this, EncryptionAlgorithm, params);
|
|
||||||
this._sessionPrepared = false;
|
|
||||||
this._prepPromise = null;
|
|
||||||
}
|
|
||||||
utils.inherits(OlmEncryption, EncryptionAlgorithm);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
|
|
||||||
* @param {string[]} roomMembers list of currently-joined users in the room
|
|
||||||
* @return {Promise} Promise which resolves when setup is complete
|
|
||||||
*/
|
|
||||||
OlmEncryption.prototype._ensureSession = function(roomMembers) {
|
|
||||||
if (this._prepPromise) {
|
|
||||||
// prep already in progress
|
|
||||||
return this._prepPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._sessionPrepared) {
|
|
||||||
// prep already done
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
const self = this;
|
|
||||||
this._prepPromise = self._crypto.downloadKeys(roomMembers).then(function(res) {
|
|
||||||
return self._crypto.ensureOlmSessionsForUsers(roomMembers);
|
|
||||||
}).then(function() {
|
|
||||||
self._sessionPrepared = true;
|
|
||||||
}).finally(function() {
|
|
||||||
self._prepPromise = null;
|
|
||||||
});
|
|
||||||
return this._prepPromise;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @param {module:models/room} room
|
|
||||||
* @param {string} eventType
|
|
||||||
* @param {object} content plaintext event content
|
|
||||||
*
|
|
||||||
* @return {Promise} Promise which resolves to the new event body
|
|
||||||
*/
|
|
||||||
OlmEncryption.prototype.encryptMessage = async function(room, eventType, content) {
|
|
||||||
// pick the list of recipients based on the membership list.
|
|
||||||
//
|
|
||||||
// TODO: there is a race condition here! What if a new user turns up
|
|
||||||
// just as you are sending a secret message?
|
|
||||||
|
|
||||||
const members = await room.getEncryptionTargetMembers();
|
|
||||||
|
|
||||||
const users = members.map(function(u) {
|
|
||||||
return u.userId;
|
|
||||||
});
|
|
||||||
|
|
||||||
const self = this;
|
|
||||||
await this._ensureSession(users);
|
|
||||||
|
|
||||||
const payloadFields = {
|
|
||||||
room_id: room.roomId,
|
|
||||||
type: eventType,
|
|
||||||
content: content,
|
|
||||||
};
|
|
||||||
|
|
||||||
const encryptedContent = {
|
|
||||||
algorithm: olmlib.OLM_ALGORITHM,
|
|
||||||
sender_key: self._olmDevice.deviceCurve25519Key,
|
|
||||||
ciphertext: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < users.length; ++i) {
|
|
||||||
const userId = users[i];
|
|
||||||
const devices = self._crypto.getStoredDevicesForUser(userId);
|
|
||||||
|
|
||||||
for (let j = 0; j < devices.length; ++j) {
|
|
||||||
const deviceInfo = devices[j];
|
|
||||||
const key = deviceInfo.getIdentityKey();
|
|
||||||
if (key == self._olmDevice.deviceCurve25519Key) {
|
|
||||||
// don't bother sending to ourself
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (deviceInfo.verified == DeviceVerification.BLOCKED) {
|
|
||||||
// don't bother setting up sessions with blocked users
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
promises.push(
|
|
||||||
olmlib.encryptMessageForDevice(
|
|
||||||
encryptedContent.ciphertext,
|
|
||||||
self._userId, self._deviceId, self._olmDevice,
|
|
||||||
userId, deviceInfo, payloadFields,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Promise.all(promises).then(() => encryptedContent);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Olm decryption implementation
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @extends {module:crypto/algorithms/DecryptionAlgorithm}
|
|
||||||
* @param {object} params parameters, as per
|
|
||||||
* {@link module:crypto/algorithms/DecryptionAlgorithm}
|
|
||||||
*/
|
|
||||||
function OlmDecryption(params) {
|
|
||||||
polyfillSuper(this, DecryptionAlgorithm, params);
|
|
||||||
}
|
|
||||||
utils.inherits(OlmDecryption, DecryptionAlgorithm);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @param {MatrixEvent} event
|
|
||||||
*
|
|
||||||
* returns a promise which resolves to a
|
|
||||||
* {@link module:crypto~EventDecryptionResult} once we have finished
|
|
||||||
* decrypting. Rejects with an `algorithms.DecryptionError` if there is a
|
|
||||||
* problem decrypting the event.
|
|
||||||
*/
|
|
||||||
OlmDecryption.prototype.decryptEvent = async function(event) {
|
|
||||||
const content = event.getWireContent();
|
|
||||||
const deviceKey = content.sender_key;
|
|
||||||
const ciphertext = content.ciphertext;
|
|
||||||
|
|
||||||
if (!ciphertext) {
|
|
||||||
throw new DecryptionError(
|
|
||||||
"OLM_MISSING_CIPHERTEXT",
|
|
||||||
"Missing ciphertext",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(this._olmDevice.deviceCurve25519Key in ciphertext)) {
|
|
||||||
throw new DecryptionError(
|
|
||||||
"OLM_NOT_INCLUDED_IN_RECIPIENTS",
|
|
||||||
"Not included in recipients",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const message = ciphertext[this._olmDevice.deviceCurve25519Key];
|
|
||||||
let payloadString;
|
|
||||||
|
|
||||||
try {
|
|
||||||
payloadString = await this._decryptMessage(deviceKey, message);
|
|
||||||
} catch (e) {
|
|
||||||
throw new DecryptionError(
|
|
||||||
"OLM_BAD_ENCRYPTED_MESSAGE",
|
|
||||||
"Bad Encrypted Message", {
|
|
||||||
sender: deviceKey,
|
|
||||||
err: e,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = JSON.parse(payloadString);
|
|
||||||
|
|
||||||
// check that we were the intended recipient, to avoid unknown-key attack
|
|
||||||
// https://github.com/vector-im/vector-web/issues/2483
|
|
||||||
if (payload.recipient != this._userId) {
|
|
||||||
throw new DecryptionError(
|
|
||||||
"OLM_BAD_RECIPIENT",
|
|
||||||
"Message was intented for " + payload.recipient,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payload.recipient_keys.ed25519 != this._olmDevice.deviceEd25519Key) {
|
|
||||||
throw new DecryptionError(
|
|
||||||
"OLM_BAD_RECIPIENT_KEY",
|
|
||||||
"Message not intended for this device", {
|
|
||||||
intended: payload.recipient_keys.ed25519,
|
|
||||||
our_key: this._olmDevice.deviceEd25519Key,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that the original sender matches what the homeserver told us, to
|
|
||||||
// avoid people masquerading as others.
|
|
||||||
// (this check is also provided via the sender's embedded ed25519 key,
|
|
||||||
// which is checked elsewhere).
|
|
||||||
if (payload.sender != event.getSender()) {
|
|
||||||
throw new DecryptionError(
|
|
||||||
"OLM_FORWARDED_MESSAGE",
|
|
||||||
"Message forwarded from " + payload.sender, {
|
|
||||||
reported_sender: event.getSender(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Olm events intended for a room have a room_id.
|
|
||||||
if (payload.room_id !== event.getRoomId()) {
|
|
||||||
throw new DecryptionError(
|
|
||||||
"OLM_BAD_ROOM",
|
|
||||||
"Message intended for room " + payload.room_id, {
|
|
||||||
reported_room: event.room_id,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const claimedKeys = payload.keys || {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
clearEvent: payload,
|
|
||||||
senderCurve25519Key: deviceKey,
|
|
||||||
claimedEd25519Key: claimedKeys.ed25519 || null,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to decrypt an Olm message
|
|
||||||
*
|
|
||||||
* @param {string} theirDeviceIdentityKey Curve25519 identity key of the sender
|
|
||||||
* @param {object} message message object, with 'type' and 'body' fields
|
|
||||||
*
|
|
||||||
* @return {string} payload, if decrypted successfully.
|
|
||||||
*/
|
|
||||||
OlmDecryption.prototype._decryptMessage = async function(
|
|
||||||
theirDeviceIdentityKey, message,
|
|
||||||
) {
|
|
||||||
// This is a wrapper that serialises decryptions of prekey messages, because
|
|
||||||
// otherwise we race between deciding we have no active sessions for the message
|
|
||||||
// and creating a new one, which we can only do once because it removes the OTK.
|
|
||||||
if (message.type !== 0) {
|
|
||||||
// not a prekey message: we can safely just try & decrypt it
|
|
||||||
return this._reallyDecryptMessage(theirDeviceIdentityKey, message);
|
|
||||||
} else {
|
|
||||||
const myPromise = this._olmDevice._olmPrekeyPromise.then(() => {
|
|
||||||
return this._reallyDecryptMessage(theirDeviceIdentityKey, message);
|
|
||||||
});
|
|
||||||
// we want the error, but don't propagate it to the next decryption
|
|
||||||
this._olmDevice._olmPrekeyPromise = myPromise.catch(() => {});
|
|
||||||
return await myPromise;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
OlmDecryption.prototype._reallyDecryptMessage = async function(
|
|
||||||
theirDeviceIdentityKey, message,
|
|
||||||
) {
|
|
||||||
const sessionIds = await this._olmDevice.getSessionIdsForDevice(
|
|
||||||
theirDeviceIdentityKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
// try each session in turn.
|
|
||||||
const decryptionErrors = {};
|
|
||||||
for (let i = 0; i < sessionIds.length; i++) {
|
|
||||||
const sessionId = sessionIds[i];
|
|
||||||
try {
|
|
||||||
const payload = await this._olmDevice.decryptMessage(
|
|
||||||
theirDeviceIdentityKey, sessionId, message.type, message.body,
|
|
||||||
);
|
|
||||||
logger.log(
|
|
||||||
"Decrypted Olm message from " + theirDeviceIdentityKey +
|
|
||||||
" with session " + sessionId,
|
|
||||||
);
|
|
||||||
return payload;
|
|
||||||
} catch (e) {
|
|
||||||
const foundSession = await this._olmDevice.matchesSession(
|
|
||||||
theirDeviceIdentityKey, sessionId, message.type, message.body,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (foundSession) {
|
|
||||||
// decryption failed, but it was a prekey message matching this
|
|
||||||
// session, so it should have worked.
|
|
||||||
throw new Error(
|
|
||||||
"Error decrypting prekey message with existing session id " +
|
|
||||||
sessionId + ": " + e.message,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise it's probably a message for another session; carry on, but
|
|
||||||
// keep a record of the error
|
|
||||||
decryptionErrors[sessionId] = e.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.type !== 0) {
|
|
||||||
// not a prekey message, so it should have matched an existing session, but it
|
|
||||||
// didn't work.
|
|
||||||
|
|
||||||
if (sessionIds.length === 0) {
|
|
||||||
throw new Error("No existing sessions");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
"Error decrypting non-prekey message with existing sessions: " +
|
|
||||||
JSON.stringify(decryptionErrors),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// prekey message which doesn't match any existing sessions: make a new
|
|
||||||
// session.
|
|
||||||
|
|
||||||
let res;
|
|
||||||
try {
|
|
||||||
res = await this._olmDevice.createInboundSession(
|
|
||||||
theirDeviceIdentityKey, message.type, message.body,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
decryptionErrors["(new)"] = e.message;
|
|
||||||
throw new Error(
|
|
||||||
"Error decrypting prekey message: " +
|
|
||||||
JSON.stringify(decryptionErrors),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.log(
|
|
||||||
"created new inbound Olm session ID " +
|
|
||||||
res.session_id + " with " + theirDeviceIdentityKey,
|
|
||||||
);
|
|
||||||
return res.payload;
|
|
||||||
};
|
|
||||||
|
|
||||||
registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);
|
|
||||||
355
src/crypto/algorithms/olm.ts
Normal file
355
src/crypto/algorithms/olm.ts
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines m.olm encryption/decryption
|
||||||
|
*
|
||||||
|
* @module crypto/algorithms/olm
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import * as olmlib from "../olmlib";
|
||||||
|
import { DeviceInfo } from "../deviceinfo";
|
||||||
|
import {
|
||||||
|
DecryptionAlgorithm,
|
||||||
|
DecryptionError,
|
||||||
|
EncryptionAlgorithm,
|
||||||
|
registerAlgorithm,
|
||||||
|
} from "./base";
|
||||||
|
import { Room } from '../../models/room';
|
||||||
|
import { MatrixEvent } from "../..";
|
||||||
|
import { IEventDecryptionResult } from "../index";
|
||||||
|
|
||||||
|
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||||
|
|
||||||
|
interface IMessage {
|
||||||
|
type: number | string;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Olm encryption implementation
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @extends {module:crypto/algorithms/EncryptionAlgorithm}
|
||||||
|
*
|
||||||
|
* @param {object} params parameters, as per
|
||||||
|
* {@link module:crypto/algorithms/EncryptionAlgorithm}
|
||||||
|
*/
|
||||||
|
class OlmEncryption extends EncryptionAlgorithm {
|
||||||
|
private sessionPrepared = false;
|
||||||
|
private prepPromise: Promise<void> = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
|
||||||
|
* @param {string[]} roomMembers list of currently-joined users in the room
|
||||||
|
* @return {Promise} Promise which resolves when setup is complete
|
||||||
|
*/
|
||||||
|
private ensureSession(roomMembers: string[]): Promise<void> {
|
||||||
|
if (this.prepPromise) {
|
||||||
|
// prep already in progress
|
||||||
|
return this.prepPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sessionPrepared) {
|
||||||
|
// prep already done
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prepPromise = this.crypto.downloadKeys(roomMembers).then((res) => {
|
||||||
|
return this.crypto.ensureOlmSessionsForUsers(roomMembers);
|
||||||
|
}).then(() => {
|
||||||
|
this.sessionPrepared = true;
|
||||||
|
}).finally(() => {
|
||||||
|
this.prepPromise = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.prepPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*
|
||||||
|
* @param {module:models/room} room
|
||||||
|
* @param {string} eventType
|
||||||
|
* @param {object} content plaintext event content
|
||||||
|
*
|
||||||
|
* @return {Promise} Promise which resolves to the new event body
|
||||||
|
*/
|
||||||
|
public async encryptMessage(room: Room, eventType: string, content: object): Promise<object> {
|
||||||
|
// pick the list of recipients based on the membership list.
|
||||||
|
//
|
||||||
|
// TODO: there is a race condition here! What if a new user turns up
|
||||||
|
// just as you are sending a secret message?
|
||||||
|
|
||||||
|
const members = await room.getEncryptionTargetMembers();
|
||||||
|
|
||||||
|
const users = members.map(function(u) {
|
||||||
|
return u.userId;
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.ensureSession(users);
|
||||||
|
|
||||||
|
const payloadFields = {
|
||||||
|
room_id: room.roomId,
|
||||||
|
type: eventType,
|
||||||
|
content: content,
|
||||||
|
};
|
||||||
|
|
||||||
|
const encryptedContent = {
|
||||||
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
|
sender_key: this.olmDevice.deviceCurve25519Key,
|
||||||
|
ciphertext: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < users.length; ++i) {
|
||||||
|
const userId = users[i];
|
||||||
|
const devices = this.crypto.getStoredDevicesForUser(userId);
|
||||||
|
|
||||||
|
for (let j = 0; j < devices.length; ++j) {
|
||||||
|
const deviceInfo = devices[j];
|
||||||
|
const key = deviceInfo.getIdentityKey();
|
||||||
|
if (key == this.olmDevice.deviceCurve25519Key) {
|
||||||
|
// don't bother sending to ourself
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (deviceInfo.verified == DeviceVerification.BLOCKED) {
|
||||||
|
// don't bother setting up sessions with blocked users
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
promises.push(
|
||||||
|
olmlib.encryptMessageForDevice(
|
||||||
|
encryptedContent.ciphertext,
|
||||||
|
this.userId, this.deviceId, this.olmDevice,
|
||||||
|
userId, deviceInfo, payloadFields,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Promise.all(promises).then(() => encryptedContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Olm decryption implementation
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @extends {module:crypto/algorithms/DecryptionAlgorithm}
|
||||||
|
* @param {object} params parameters, as per
|
||||||
|
* {@link module:crypto/algorithms/DecryptionAlgorithm}
|
||||||
|
*/
|
||||||
|
class OlmDecryption extends DecryptionAlgorithm {
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*
|
||||||
|
* @param {MatrixEvent} event
|
||||||
|
*
|
||||||
|
* returns a promise which resolves to a
|
||||||
|
* {@link module:crypto~EventDecryptionResult} once we have finished
|
||||||
|
* decrypting. Rejects with an `algorithms.DecryptionError` if there is a
|
||||||
|
* problem decrypting the event.
|
||||||
|
*/
|
||||||
|
public async decryptEvent(event: MatrixEvent): Promise<IEventDecryptionResult> {
|
||||||
|
const content = event.getWireContent();
|
||||||
|
const deviceKey = content.sender_key;
|
||||||
|
const ciphertext = content.ciphertext;
|
||||||
|
|
||||||
|
if (!ciphertext) {
|
||||||
|
throw new DecryptionError(
|
||||||
|
"OLM_MISSING_CIPHERTEXT",
|
||||||
|
"Missing ciphertext",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(this.olmDevice.deviceCurve25519Key in ciphertext)) {
|
||||||
|
throw new DecryptionError(
|
||||||
|
"OLM_NOT_INCLUDED_IN_RECIPIENTS",
|
||||||
|
"Not included in recipients",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const message = ciphertext[this.olmDevice.deviceCurve25519Key];
|
||||||
|
let payloadString;
|
||||||
|
|
||||||
|
try {
|
||||||
|
payloadString = await this.decryptMessage(deviceKey, message);
|
||||||
|
} catch (e) {
|
||||||
|
throw new DecryptionError(
|
||||||
|
"OLM_BAD_ENCRYPTED_MESSAGE",
|
||||||
|
"Bad Encrypted Message", {
|
||||||
|
sender: deviceKey,
|
||||||
|
err: e,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = JSON.parse(payloadString);
|
||||||
|
|
||||||
|
// check that we were the intended recipient, to avoid unknown-key attack
|
||||||
|
// https://github.com/vector-im/vector-web/issues/2483
|
||||||
|
if (payload.recipient != this.userId) {
|
||||||
|
throw new DecryptionError(
|
||||||
|
"OLM_BAD_RECIPIENT",
|
||||||
|
"Message was intented for " + payload.recipient,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.recipient_keys.ed25519 != this.olmDevice.deviceEd25519Key) {
|
||||||
|
throw new DecryptionError(
|
||||||
|
"OLM_BAD_RECIPIENT_KEY",
|
||||||
|
"Message not intended for this device", {
|
||||||
|
intended: payload.recipient_keys.ed25519,
|
||||||
|
our_key: this.olmDevice.deviceEd25519Key,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the original sender matches what the homeserver told us, to
|
||||||
|
// avoid people masquerading as others.
|
||||||
|
// (this check is also provided via the sender's embedded ed25519 key,
|
||||||
|
// which is checked elsewhere).
|
||||||
|
if (payload.sender != event.getSender()) {
|
||||||
|
throw new DecryptionError(
|
||||||
|
"OLM_FORWARDED_MESSAGE",
|
||||||
|
"Message forwarded from " + payload.sender, {
|
||||||
|
reported_sender: event.getSender(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Olm events intended for a room have a room_id.
|
||||||
|
if (payload.room_id !== event.getRoomId()) {
|
||||||
|
throw new DecryptionError(
|
||||||
|
"OLM_BAD_ROOM",
|
||||||
|
"Message intended for room " + payload.room_id, {
|
||||||
|
reported_room: event.getRoomId(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimedKeys = payload.keys || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
clearEvent: payload,
|
||||||
|
senderCurve25519Key: deviceKey,
|
||||||
|
claimedEd25519Key: claimedKeys.ed25519 || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to decrypt an Olm message
|
||||||
|
*
|
||||||
|
* @param {string} theirDeviceIdentityKey Curve25519 identity key of the sender
|
||||||
|
* @param {object} message message object, with 'type' and 'body' fields
|
||||||
|
*
|
||||||
|
* @return {string} payload, if decrypted successfully.
|
||||||
|
*/
|
||||||
|
private async decryptMessage(theirDeviceIdentityKey: string, message: IMessage): Promise<string> {
|
||||||
|
// This is a wrapper that serialises decryptions of prekey messages, because
|
||||||
|
// otherwise we race between deciding we have no active sessions for the message
|
||||||
|
// and creating a new one, which we can only do once because it removes the OTK.
|
||||||
|
if (message.type !== 0) {
|
||||||
|
// not a prekey message: we can safely just try & decrypt it
|
||||||
|
return this.reallyDecryptMessage(theirDeviceIdentityKey, message);
|
||||||
|
} else {
|
||||||
|
const myPromise = this.olmDevice._olmPrekeyPromise.then(() => {
|
||||||
|
return this.reallyDecryptMessage(theirDeviceIdentityKey, message);
|
||||||
|
});
|
||||||
|
// we want the error, but don't propagate it to the next decryption
|
||||||
|
this.olmDevice._olmPrekeyPromise = myPromise.catch(() => {});
|
||||||
|
return await myPromise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async reallyDecryptMessage(theirDeviceIdentityKey: string, message: IMessage): Promise<string> {
|
||||||
|
const sessionIds = await this.olmDevice.getSessionIdsForDevice(theirDeviceIdentityKey);
|
||||||
|
|
||||||
|
// try each session in turn.
|
||||||
|
const decryptionErrors = {};
|
||||||
|
for (let i = 0; i < sessionIds.length; i++) {
|
||||||
|
const sessionId = sessionIds[i];
|
||||||
|
try {
|
||||||
|
const payload = await this.olmDevice.decryptMessage(
|
||||||
|
theirDeviceIdentityKey, sessionId, message.type, message.body,
|
||||||
|
);
|
||||||
|
logger.log(
|
||||||
|
"Decrypted Olm message from " + theirDeviceIdentityKey +
|
||||||
|
" with session " + sessionId,
|
||||||
|
);
|
||||||
|
return payload;
|
||||||
|
} catch (e) {
|
||||||
|
const foundSession = await this.olmDevice.matchesSession(
|
||||||
|
theirDeviceIdentityKey, sessionId, message.type, message.body,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (foundSession) {
|
||||||
|
// decryption failed, but it was a prekey message matching this
|
||||||
|
// session, so it should have worked.
|
||||||
|
throw new Error(
|
||||||
|
"Error decrypting prekey message with existing session id " +
|
||||||
|
sessionId + ": " + e.message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise it's probably a message for another session; carry on, but
|
||||||
|
// keep a record of the error
|
||||||
|
decryptionErrors[sessionId] = e.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type !== 0) {
|
||||||
|
// not a prekey message, so it should have matched an existing session, but it
|
||||||
|
// didn't work.
|
||||||
|
|
||||||
|
if (sessionIds.length === 0) {
|
||||||
|
throw new Error("No existing sessions");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
"Error decrypting non-prekey message with existing sessions: " +
|
||||||
|
JSON.stringify(decryptionErrors),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// prekey message which doesn't match any existing sessions: make a new
|
||||||
|
// session.
|
||||||
|
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await this.olmDevice.createInboundSession(
|
||||||
|
theirDeviceIdentityKey, message.type, message.body,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
decryptionErrors["(new)"] = e.message;
|
||||||
|
throw new Error(
|
||||||
|
"Error decrypting prekey message: " +
|
||||||
|
JSON.stringify(decryptionErrors),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log(
|
||||||
|
"created new inbound Olm session ID " +
|
||||||
|
res.session_id + " with " + theirDeviceIdentityKey,
|
||||||
|
);
|
||||||
|
return res.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);
|
||||||
@@ -28,7 +28,7 @@ import { ReEmitter } from '../ReEmitter';
|
|||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
import { OlmDevice } from "./OlmDevice";
|
import { OlmDevice } from "./OlmDevice";
|
||||||
import * as olmlib from "./olmlib";
|
import * as olmlib from "./olmlib";
|
||||||
import { DeviceList } from "./DeviceList";
|
import {DeviceInfoMap, DeviceList } from "./DeviceList";
|
||||||
import { DeviceInfo, IDevice } from "./deviceinfo";
|
import { DeviceInfo, IDevice } from "./deviceinfo";
|
||||||
import * as algorithms from "./algorithms";
|
import * as algorithms from "./algorithms";
|
||||||
import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from './CrossSigning';
|
import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from './CrossSigning';
|
||||||
@@ -1923,10 +1923,7 @@ export class Crypto extends EventEmitter {
|
|||||||
* @return {Promise} A promise which resolves to a map userId->deviceId->{@link
|
* @return {Promise} A promise which resolves to a map userId->deviceId->{@link
|
||||||
* module:crypto/deviceinfo|DeviceInfo}.
|
* module:crypto/deviceinfo|DeviceInfo}.
|
||||||
*/
|
*/
|
||||||
public downloadKeys(
|
public downloadKeys(userIds: string[], forceDownload?: boolean): Promise<DeviceInfoMap> {
|
||||||
userIds: string[],
|
|
||||||
forceDownload?: boolean,
|
|
||||||
): Promise<Record<string, Record<string, IDevice>>> {
|
|
||||||
return this.deviceList.downloadKeys(userIds, forceDownload);
|
return this.deviceList.downloadKeys(userIds, forceDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ interface IDecryptionResult {
|
|||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
interface IClearEvent {
|
export interface IClearEvent {
|
||||||
type: string;
|
type: string;
|
||||||
content: Omit<IContent, "membership" | "avatar_url" | "displayname" | "m.relates_to">;
|
content: Omit<IContent, "membership" | "avatar_url" | "displayname" | "m.relates_to">;
|
||||||
unsigned?: IUnsigned;
|
unsigned?: IUnsigned;
|
||||||
|
|||||||
Reference in New Issue
Block a user