You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-28 05:03:59 +03:00
Various changes to src/crypto files for correctness (#2137)
* make various changes for correctness * apply some review feedback * Address some review feedback * add some more correctness * refactor ensureOutboundSession to fit types better * change variable naming slightly to prevent confusion * some wording around exception-catching * Tidy test * Simplify * Add tests * Add more test coverage * Apply suggestions from code review Co-authored-by: Travis Ralston <travpc@gmail.com> * Update crypto.spec.js * Update spec/unit/crypto.spec.js Co-authored-by: Faye Duxovni <duxovni@duxovni.org> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: Travis Ralston <travpc@gmail.com> Co-authored-by: Faye Duxovni <duxovni@duxovni.org>
This commit is contained in:
@@ -179,7 +179,7 @@ export abstract class DecryptionAlgorithm {
|
||||
*
|
||||
* @param {module:models/event.MatrixEvent} params event key event
|
||||
*/
|
||||
public onRoomKeyEvent(params: MatrixEvent): void {
|
||||
public async onRoomKeyEvent(params: MatrixEvent): Promise<void> {
|
||||
// ignore by default
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,8 @@ class OutboundSessionInfo {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +233,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
// are using, and which devices we have shared the keys with. It resolves
|
||||
// with an OutboundSessionInfo (or undefined, for the first message in the
|
||||
// room).
|
||||
private setupPromise = Promise.resolve<OutboundSessionInfo>(undefined);
|
||||
private setupPromise = Promise.resolve<OutboundSessionInfo | null>(null);
|
||||
|
||||
// Map of outbound sessions by sessions ID. Used if we need a particular
|
||||
// session (the session we're currently using to send is always obtained
|
||||
@@ -240,8 +242,8 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
|
||||
private readonly sessionRotationPeriodMsgs: number;
|
||||
private readonly sessionRotationPeriodMs: number;
|
||||
private encryptionPreparation: Promise<void>;
|
||||
private encryptionPreparationMetadata: {
|
||||
private encryptionPreparation?: {
|
||||
promise: Promise<void>;
|
||||
startTime: number;
|
||||
};
|
||||
|
||||
@@ -270,193 +272,209 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
blocked: IBlockedMap,
|
||||
singleOlmCreationPhase = false,
|
||||
): Promise<OutboundSessionInfo> {
|
||||
let session: OutboundSessionInfo;
|
||||
|
||||
// takes the previous OutboundSessionInfo, and considers whether to create
|
||||
// a new one. Also shares the key with any (new) devices in the room.
|
||||
// Updates `session` to hold the final OutboundSessionInfo.
|
||||
//
|
||||
// Returns the successful session whether keyshare succeeds or not.
|
||||
//
|
||||
// returns a promise which resolves once the keyshare is successful.
|
||||
const prepareSession = async (oldSession: OutboundSessionInfo) => {
|
||||
session = oldSession;
|
||||
|
||||
const setup = async (oldSession: OutboundSessionInfo | null): Promise<OutboundSessionInfo> => {
|
||||
const sharedHistory = isRoomSharedHistory(room);
|
||||
|
||||
// history visibility changed
|
||||
if (session && sharedHistory !== session.sharedHistory) {
|
||||
session = null;
|
||||
const session = await this.prepareSession(devicesInRoom, sharedHistory, oldSession);
|
||||
|
||||
try {
|
||||
await this.shareSession(devicesInRoom, sharedHistory, singleOlmCreationPhase, blocked, session);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to ensure outbound session in ${this.roomId}`, e);
|
||||
}
|
||||
|
||||
// need to make a brand new session?
|
||||
if (session && session.needsRotation(this.sessionRotationPeriodMsgs,
|
||||
this.sessionRotationPeriodMs)
|
||||
) {
|
||||
logger.log("Starting new megolm session because we need to rotate.");
|
||||
session = null;
|
||||
}
|
||||
|
||||
// determine if we have shared with anyone we shouldn't have
|
||||
if (session && session.sharedWithTooManyDevices(devicesInRoom)) {
|
||||
session = null;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
logger.log(`Starting new megolm session for room ${this.roomId}`);
|
||||
session = await this.prepareNewSession(sharedHistory);
|
||||
logger.log(`Started new megolm session ${session.sessionId} ` +
|
||||
`for room ${this.roomId}`);
|
||||
this.outboundSessions[session.sessionId] = session;
|
||||
}
|
||||
|
||||
// now check if we need to share with any devices
|
||||
const shareMap: Record<string, DeviceInfo[]> = {};
|
||||
|
||||
for (const [userId, userDevices] of Object.entries(devicesInRoom)) {
|
||||
for (const [deviceId, deviceInfo] of Object.entries(userDevices)) {
|
||||
const key = deviceInfo.getIdentityKey();
|
||||
if (key == this.olmDevice.deviceCurve25519Key) {
|
||||
// don't bother sending to ourself
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!session.sharedWithDevices[userId] ||
|
||||
session.sharedWithDevices[userId][deviceId] === undefined
|
||||
) {
|
||||
shareMap[userId] = shareMap[userId] || [];
|
||||
shareMap[userId].push(deviceInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const key = this.olmDevice.getOutboundGroupSessionKey(session.sessionId);
|
||||
const payload: IPayload = {
|
||||
type: "m.room_key",
|
||||
content: {
|
||||
"algorithm": olmlib.MEGOLM_ALGORITHM,
|
||||
"room_id": this.roomId,
|
||||
"session_id": session.sessionId,
|
||||
"session_key": key.key,
|
||||
"chain_index": key.chain_index,
|
||||
"org.matrix.msc3061.shared_history": sharedHistory,
|
||||
},
|
||||
};
|
||||
const [devicesWithoutSession, olmSessions] = await olmlib.getExistingOlmSessions(
|
||||
this.olmDevice, this.baseApis, shareMap,
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
// share keys with devices that we already have a session for
|
||||
logger.debug(`Sharing keys with existing Olm sessions in ${this.roomId}`, olmSessions);
|
||||
await this.shareKeyWithOlmSessions(session, key, payload, olmSessions);
|
||||
logger.debug(`Shared keys with existing Olm sessions in ${this.roomId}`);
|
||||
})(),
|
||||
(async () => {
|
||||
logger.debug(
|
||||
`Sharing keys (start phase 1) with new Olm sessions in ${this.roomId}`,
|
||||
devicesWithoutSession,
|
||||
);
|
||||
const errorDevices: IOlmDevice[] = [];
|
||||
|
||||
// meanwhile, establish olm sessions for devices that we don't
|
||||
// already have a session for, and share keys with them. If
|
||||
// we're doing two phases of olm session creation, use a
|
||||
// shorter timeout when fetching one-time keys for the first
|
||||
// phase.
|
||||
const start = Date.now();
|
||||
const failedServers: string[] = [];
|
||||
await this.shareKeyWithDevices(
|
||||
session, key, payload, devicesWithoutSession, errorDevices,
|
||||
singleOlmCreationPhase ? 10000 : 2000, failedServers,
|
||||
);
|
||||
logger.debug(`Shared keys (end phase 1) with new Olm sessions in ${this.roomId}`);
|
||||
|
||||
if (!singleOlmCreationPhase && (Date.now() - start < 10000)) {
|
||||
// perform the second phase of olm session creation if requested,
|
||||
// and if the first phase didn't take too long
|
||||
(async () => {
|
||||
// Retry sending keys to devices that we were unable to establish
|
||||
// an olm session for. This time, we use a longer timeout, but we
|
||||
// do this in the background and don't block anything else while we
|
||||
// do this. We only need to retry users from servers that didn't
|
||||
// respond the first time.
|
||||
const retryDevices: Record<string, DeviceInfo[]> = {};
|
||||
const failedServerMap = new Set;
|
||||
for (const server of failedServers) {
|
||||
failedServerMap.add(server);
|
||||
}
|
||||
const failedDevices = [];
|
||||
for (const { userId, deviceInfo } of errorDevices) {
|
||||
const userHS = userId.slice(userId.indexOf(":") + 1);
|
||||
if (failedServerMap.has(userHS)) {
|
||||
retryDevices[userId] = retryDevices[userId] || [];
|
||||
retryDevices[userId].push(deviceInfo);
|
||||
} else {
|
||||
// if we aren't going to retry, then handle it
|
||||
// as a failed device
|
||||
failedDevices.push({ userId, deviceInfo });
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Sharing keys (start phase 2) with new Olm sessions in ${this.roomId}`);
|
||||
await this.shareKeyWithDevices(
|
||||
session, key, payload, retryDevices, failedDevices, 30000,
|
||||
);
|
||||
logger.debug(`Shared keys (end phase 2) with new Olm sessions in ${this.roomId}`);
|
||||
|
||||
await this.notifyFailedOlmDevices(session, key, failedDevices);
|
||||
})();
|
||||
} else {
|
||||
await this.notifyFailedOlmDevices(session, key, errorDevices);
|
||||
}
|
||||
logger.debug(`Shared keys (all phases done) with new Olm sessions in ${this.roomId}`);
|
||||
})(),
|
||||
(async () => {
|
||||
logger.debug(`There are ${Object.entries(blocked).length} blocked devices in ${this.roomId}`,
|
||||
Object.entries(blocked));
|
||||
|
||||
// also, notify newly blocked devices that they're blocked
|
||||
logger.debug(`Notifying newly blocked devices in ${this.roomId}`);
|
||||
const blockedMap: Record<string, Record<string, { device: IBlockedDevice }>> = {};
|
||||
let blockedCount = 0;
|
||||
for (const [userId, userBlockedDevices] of Object.entries(blocked)) {
|
||||
for (const [deviceId, device] of Object.entries(userBlockedDevices)) {
|
||||
if (
|
||||
!session.blockedDevicesNotified[userId] ||
|
||||
session.blockedDevicesNotified[userId][deviceId] === undefined
|
||||
) {
|
||||
blockedMap[userId] = blockedMap[userId] || {};
|
||||
blockedMap[userId][deviceId] = { device };
|
||||
blockedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.notifyBlockedDevices(session, blockedMap);
|
||||
logger.debug(`Notified ${blockedCount} newly blocked devices in ${this.roomId}`, blockedMap);
|
||||
})(),
|
||||
]);
|
||||
return session;
|
||||
};
|
||||
|
||||
// helper which returns the session prepared by prepareSession
|
||||
function returnSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
// first wait for the previous share to complete
|
||||
const prom = this.setupPromise.then(prepareSession);
|
||||
const prom = this.setupPromise.then(setup);
|
||||
|
||||
// Ensure any failures are logged for debugging
|
||||
prom.catch(e => {
|
||||
logger.error(`Failed to ensure outbound session in ${this.roomId}`, e);
|
||||
logger.error(`Failed to setup outbound session in ${this.roomId}`, e);
|
||||
});
|
||||
|
||||
// setupPromise resolves to `session` whether or not the share succeeds
|
||||
this.setupPromise = prom.then(returnSession, returnSession);
|
||||
this.setupPromise = prom;
|
||||
|
||||
// but we return a promise which only resolves if the share was successful.
|
||||
return prom.then(returnSession);
|
||||
return prom;
|
||||
}
|
||||
|
||||
private async prepareSession(
|
||||
devicesInRoom: DeviceInfoMap,
|
||||
sharedHistory: boolean,
|
||||
session: OutboundSessionInfo | null,
|
||||
): Promise<OutboundSessionInfo> {
|
||||
// history visibility changed
|
||||
if (session && sharedHistory !== session.sharedHistory) {
|
||||
session = null;
|
||||
}
|
||||
|
||||
// need to make a brand new session?
|
||||
if (session?.needsRotation(this.sessionRotationPeriodMsgs, this.sessionRotationPeriodMs)) {
|
||||
logger.log("Starting new megolm session because we need to rotate.");
|
||||
session = null;
|
||||
}
|
||||
|
||||
// determine if we have shared with anyone we shouldn't have
|
||||
if (session?.sharedWithTooManyDevices(devicesInRoom)) {
|
||||
session = null;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
logger.log(`Starting new megolm session for room ${this.roomId}`);
|
||||
session = await this.prepareNewSession(sharedHistory);
|
||||
logger.log(`Started new megolm session ${session.sessionId} ` +
|
||||
`for room ${this.roomId}`);
|
||||
this.outboundSessions[session.sessionId] = session;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
private async shareSession(
|
||||
devicesInRoom: DeviceInfoMap,
|
||||
sharedHistory: boolean,
|
||||
singleOlmCreationPhase: boolean,
|
||||
blocked: IBlockedMap,
|
||||
session: OutboundSessionInfo,
|
||||
) {
|
||||
// now check if we need to share with any devices
|
||||
const shareMap: Record<string, DeviceInfo[]> = {};
|
||||
|
||||
for (const [userId, userDevices] of Object.entries(devicesInRoom)) {
|
||||
for (const [deviceId, deviceInfo] of Object.entries(userDevices)) {
|
||||
const key = deviceInfo.getIdentityKey();
|
||||
if (key == this.olmDevice.deviceCurve25519Key) {
|
||||
// don't bother sending to ourself
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!session.sharedWithDevices[userId] ||
|
||||
session.sharedWithDevices[userId][deviceId] === undefined
|
||||
) {
|
||||
shareMap[userId] = shareMap[userId] || [];
|
||||
shareMap[userId].push(deviceInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const key = this.olmDevice.getOutboundGroupSessionKey(session.sessionId);
|
||||
const payload: IPayload = {
|
||||
type: "m.room_key",
|
||||
content: {
|
||||
"algorithm": olmlib.MEGOLM_ALGORITHM,
|
||||
"room_id": this.roomId,
|
||||
"session_id": session.sessionId,
|
||||
"session_key": key.key,
|
||||
"chain_index": key.chain_index,
|
||||
"org.matrix.msc3061.shared_history": sharedHistory,
|
||||
},
|
||||
};
|
||||
const [devicesWithoutSession, olmSessions] = await olmlib.getExistingOlmSessions(
|
||||
this.olmDevice, this.baseApis, shareMap,
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
// share keys with devices that we already have a session for
|
||||
logger.debug(`Sharing keys with existing Olm sessions in ${this.roomId}`, olmSessions);
|
||||
await this.shareKeyWithOlmSessions(session, key, payload, olmSessions);
|
||||
logger.debug(`Shared keys with existing Olm sessions in ${this.roomId}`);
|
||||
})(),
|
||||
(async () => {
|
||||
logger.debug(
|
||||
`Sharing keys (start phase 1) with new Olm sessions in ${this.roomId}`,
|
||||
devicesWithoutSession,
|
||||
);
|
||||
const errorDevices: IOlmDevice[] = [];
|
||||
|
||||
// meanwhile, establish olm sessions for devices that we don't
|
||||
// already have a session for, and share keys with them. If
|
||||
// we're doing two phases of olm session creation, use a
|
||||
// shorter timeout when fetching one-time keys for the first
|
||||
// phase.
|
||||
const start = Date.now();
|
||||
const failedServers: string[] = [];
|
||||
await this.shareKeyWithDevices(
|
||||
session, key, payload, devicesWithoutSession, errorDevices,
|
||||
singleOlmCreationPhase ? 10000 : 2000, failedServers,
|
||||
);
|
||||
logger.debug(`Shared keys (end phase 1) with new Olm sessions in ${this.roomId}`);
|
||||
|
||||
if (!singleOlmCreationPhase && (Date.now() - start < 10000)) {
|
||||
// perform the second phase of olm session creation if requested,
|
||||
// and if the first phase didn't take too long
|
||||
(async () => {
|
||||
// Retry sending keys to devices that we were unable to establish
|
||||
// an olm session for. This time, we use a longer timeout, but we
|
||||
// do this in the background and don't block anything else while we
|
||||
// do this. We only need to retry users from servers that didn't
|
||||
// respond the first time.
|
||||
const retryDevices: Record<string, DeviceInfo[]> = {};
|
||||
const failedServerMap = new Set;
|
||||
for (const server of failedServers) {
|
||||
failedServerMap.add(server);
|
||||
}
|
||||
const failedDevices: IOlmDevice[] = [];
|
||||
for (const { userId, deviceInfo } of errorDevices) {
|
||||
const userHS = userId.slice(userId.indexOf(":") + 1);
|
||||
if (failedServerMap.has(userHS)) {
|
||||
retryDevices[userId] = retryDevices[userId] || [];
|
||||
retryDevices[userId].push(deviceInfo);
|
||||
} else {
|
||||
// if we aren't going to retry, then handle it
|
||||
// as a failed device
|
||||
failedDevices.push({ userId, deviceInfo });
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Sharing keys (start phase 2) with new Olm sessions in ${this.roomId}`);
|
||||
await this.shareKeyWithDevices(
|
||||
session, key, payload, retryDevices, failedDevices, 30000,
|
||||
);
|
||||
logger.debug(`Shared keys (end phase 2) with new Olm sessions in ${this.roomId}`);
|
||||
|
||||
await this.notifyFailedOlmDevices(session, key, failedDevices);
|
||||
})();
|
||||
} else {
|
||||
await this.notifyFailedOlmDevices(session, key, errorDevices);
|
||||
}
|
||||
logger.debug(`Shared keys (all phases done) with new Olm sessions in ${this.roomId}`);
|
||||
})(),
|
||||
(async () => {
|
||||
logger.debug(`There are ${Object.entries(blocked).length} blocked devices in ${this.roomId}`,
|
||||
Object.entries(blocked));
|
||||
|
||||
// also, notify newly blocked devices that they're blocked
|
||||
logger.debug(`Notifying newly blocked devices in ${this.roomId}`);
|
||||
const blockedMap: Record<string, Record<string, { device: IBlockedDevice }>> = {};
|
||||
let blockedCount = 0;
|
||||
for (const [userId, userBlockedDevices] of Object.entries(blocked)) {
|
||||
for (const [deviceId, device] of Object.entries(userBlockedDevices)) {
|
||||
if (
|
||||
!session.blockedDevicesNotified[userId] ||
|
||||
session.blockedDevicesNotified[userId][deviceId] === undefined
|
||||
) {
|
||||
blockedMap[userId] = blockedMap[userId] || {};
|
||||
blockedMap[userId][deviceId] = { device };
|
||||
blockedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.notifyBlockedDevices(session, blockedMap);
|
||||
logger.debug(`Notified ${blockedCount} newly blocked devices in ${this.roomId}`, blockedMap);
|
||||
})(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -866,7 +884,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
logger.debug(`Ensuring Olm sessions for devices in ${this.roomId}`);
|
||||
const devicemap = await olmlib.ensureOlmSessionsForDevices(
|
||||
this.olmDevice, this.baseApis, devicesByUser, false, otkTimeout, failedServers,
|
||||
logger.withPrefix(`[${this.roomId}]`),
|
||||
logger.withPrefix?.(`[${this.roomId}]`),
|
||||
);
|
||||
logger.debug(`Ensured Olm sessions for devices in ${this.roomId}`);
|
||||
|
||||
@@ -1006,11 +1024,11 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
* @param {module:models/room} room the room the event is in
|
||||
*/
|
||||
public prepareToEncrypt(room: Room): void {
|
||||
if (this.encryptionPreparation) {
|
||||
if (this.encryptionPreparation != null) {
|
||||
// We're already preparing something, so don't do anything else.
|
||||
// FIXME: check if we need to restart
|
||||
// (https://github.com/matrix-org/matrix-js-sdk/issues/1255)
|
||||
const elapsedTime = Date.now() - this.encryptionPreparationMetadata.startTime;
|
||||
const elapsedTime = Date.now() - this.encryptionPreparation.startTime;
|
||||
logger.debug(
|
||||
`Already started preparing to encrypt for ${this.roomId} ` +
|
||||
`${elapsedTime} ms ago, skipping`,
|
||||
@@ -1020,32 +1038,31 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
|
||||
logger.debug(`Preparing to encrypt events for ${this.roomId}`);
|
||||
|
||||
this.encryptionPreparationMetadata = {
|
||||
this.encryptionPreparation = {
|
||||
startTime: Date.now(),
|
||||
};
|
||||
this.encryptionPreparation = (async () => {
|
||||
try {
|
||||
logger.debug(`Getting devices in ${this.roomId}`);
|
||||
const [devicesInRoom, blocked] = await this.getDevicesInRoom(room);
|
||||
promise: (async () => {
|
||||
try {
|
||||
logger.debug(`Getting devices in ${this.roomId}`);
|
||||
const [devicesInRoom, blocked] = await this.getDevicesInRoom(room);
|
||||
|
||||
if (this.crypto.getGlobalErrorOnUnknownDevices()) {
|
||||
// Drop unknown devices for now. When the message gets sent, we'll
|
||||
// throw an error, but we'll still be prepared to send to the known
|
||||
// devices.
|
||||
this.removeUnknownDevices(devicesInRoom);
|
||||
if (this.crypto.getGlobalErrorOnUnknownDevices()) {
|
||||
// Drop unknown devices for now. When the message gets sent, we'll
|
||||
// throw an error, but we'll still be prepared to send to the known
|
||||
// devices.
|
||||
this.removeUnknownDevices(devicesInRoom);
|
||||
}
|
||||
|
||||
logger.debug(`Ensuring outbound session in ${this.roomId}`);
|
||||
await this.ensureOutboundSession(room, devicesInRoom, blocked, true);
|
||||
|
||||
logger.debug(`Ready to encrypt events for ${this.roomId}`);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to prepare to encrypt events for ${this.roomId}`, e);
|
||||
} finally {
|
||||
delete this.encryptionPreparation;
|
||||
}
|
||||
|
||||
logger.debug(`Ensuring outbound session in ${this.roomId}`);
|
||||
await this.ensureOutboundSession(room, devicesInRoom, blocked, true);
|
||||
|
||||
logger.debug(`Ready to encrypt events for ${this.roomId}`);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to prepare to encrypt events for ${this.roomId}`, e);
|
||||
} finally {
|
||||
delete this.encryptionPreparationMetadata;
|
||||
delete this.encryptionPreparation;
|
||||
}
|
||||
})();
|
||||
})(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1060,12 +1077,12 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
public async encryptMessage(room: Room, eventType: string, content: object): Promise<object> {
|
||||
logger.log(`Starting to encrypt event for ${this.roomId}`);
|
||||
|
||||
if (this.encryptionPreparation) {
|
||||
if (this.encryptionPreparation != null) {
|
||||
// If we started sending keys, wait for it to be done.
|
||||
// FIXME: check if we need to cancel
|
||||
// (https://github.com/matrix-org/matrix-js-sdk/issues/1255)
|
||||
try {
|
||||
await this.encryptionPreparation;
|
||||
await this.encryptionPreparation.promise;
|
||||
} catch (e) {
|
||||
// ignore any errors -- if the preparation failed, we'll just
|
||||
// restart everything here
|
||||
@@ -1405,7 +1422,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
if (!senderPendingEvents.has(sessionId)) {
|
||||
senderPendingEvents.set(sessionId, new Set());
|
||||
}
|
||||
senderPendingEvents.get(sessionId).add(event);
|
||||
senderPendingEvents.get(sessionId)?.add(event);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1439,17 +1456,17 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
*
|
||||
* @param {module:models/event.MatrixEvent} event key event
|
||||
*/
|
||||
public onRoomKeyEvent(event: MatrixEvent): Promise<void> {
|
||||
const content = event.getContent();
|
||||
const sessionId = content.session_id;
|
||||
public async onRoomKeyEvent(event: MatrixEvent): Promise<void> {
|
||||
const content = event.getContent<Partial<IMessage["content"]>>();
|
||||
let senderKey = event.getSenderKey();
|
||||
let forwardingKeyChain = [];
|
||||
let forwardingKeyChain: string[] = [];
|
||||
let exportFormat = false;
|
||||
let keysClaimed;
|
||||
let keysClaimed: ReturnType<MatrixEvent["getKeysClaimed"]>;
|
||||
|
||||
if (!content.room_id ||
|
||||
!sessionId ||
|
||||
!content.session_key
|
||||
!content.session_key ||
|
||||
!content.session_id ||
|
||||
!content.algorithm
|
||||
) {
|
||||
logger.error("key event is missing fields");
|
||||
return;
|
||||
@@ -1462,20 +1479,18 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
|
||||
if (event.getType() == "m.forwarded_room_key") {
|
||||
exportFormat = true;
|
||||
forwardingKeyChain = content.forwarding_curve25519_key_chain;
|
||||
if (!Array.isArray(forwardingKeyChain)) {
|
||||
forwardingKeyChain = [];
|
||||
}
|
||||
forwardingKeyChain = Array.isArray(content.forwarding_curve25519_key_chain) ?
|
||||
content.forwarding_curve25519_key_chain : [];
|
||||
|
||||
// copy content before we modify it
|
||||
forwardingKeyChain = forwardingKeyChain.slice();
|
||||
forwardingKeyChain.push(senderKey);
|
||||
|
||||
senderKey = content.sender_key;
|
||||
if (!senderKey) {
|
||||
if (!content.sender_key) {
|
||||
logger.error("forwarded_room_key event is missing sender_key field");
|
||||
return;
|
||||
}
|
||||
senderKey = content.sender_key;
|
||||
|
||||
const ed25519Key = content.sender_claimed_ed25519_key;
|
||||
if (!ed25519Key) {
|
||||
@@ -1496,34 +1511,39 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
if (content["org.matrix.msc3061.shared_history"]) {
|
||||
extraSessionData.sharedHistory = true;
|
||||
}
|
||||
return this.olmDevice.addInboundGroupSession(
|
||||
content.room_id, senderKey, forwardingKeyChain, sessionId,
|
||||
content.session_key, keysClaimed,
|
||||
exportFormat, extraSessionData,
|
||||
).then(() => {
|
||||
|
||||
try {
|
||||
await this.olmDevice.addInboundGroupSession(
|
||||
content.room_id,
|
||||
senderKey,
|
||||
forwardingKeyChain,
|
||||
content.session_id,
|
||||
content.session_key,
|
||||
keysClaimed,
|
||||
exportFormat,
|
||||
extraSessionData,
|
||||
);
|
||||
|
||||
// have another go at decrypting events sent with this session.
|
||||
this.retryDecryption(senderKey, sessionId)
|
||||
.then((success) => {
|
||||
// cancel any outstanding room key requests for this session.
|
||||
// Only do this if we managed to decrypt every message in the
|
||||
// session, because if we didn't, we leave the other key
|
||||
// requests in the hopes that someone sends us a key that
|
||||
// includes an earlier index.
|
||||
if (success) {
|
||||
this.crypto.cancelRoomKeyRequest({
|
||||
algorithm: content.algorithm,
|
||||
room_id: content.room_id,
|
||||
session_id: content.session_id,
|
||||
sender_key: senderKey,
|
||||
});
|
||||
}
|
||||
if (await this.retryDecryption(senderKey, content.session_id)) {
|
||||
// cancel any outstanding room key requests for this session.
|
||||
// Only do this if we managed to decrypt every message in the
|
||||
// session, because if we didn't, we leave the other key
|
||||
// requests in the hopes that someone sends us a key that
|
||||
// includes an earlier index.
|
||||
this.crypto.cancelRoomKeyRequest({
|
||||
algorithm: content.algorithm,
|
||||
room_id: content.room_id,
|
||||
session_id: content.session_id,
|
||||
sender_key: senderKey,
|
||||
});
|
||||
}).then(() => {
|
||||
}
|
||||
|
||||
// don't wait for the keys to be backed up for the server
|
||||
this.crypto.backupManager.backupGroupSession(senderKey, content.session_id);
|
||||
}).catch((e) => {
|
||||
await this.crypto.backupManager.backupGroupSession(senderKey, content.session_id);
|
||||
} catch (e) {
|
||||
logger.error(`Error handling m.room_key_event: ${e}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1716,7 +1736,10 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
* @param {boolean} [opts.untrusted] whether the key should be considered as untrusted
|
||||
* @param {string} [opts.source] where the key came from
|
||||
*/
|
||||
public importRoomKey(session: IMegolmSessionData, opts: any = {}): Promise<void> {
|
||||
public importRoomKey(
|
||||
session: IMegolmSessionData,
|
||||
opts: { untrusted?: boolean, source?: string } = {},
|
||||
): Promise<void> {
|
||||
const extraSessionData: any = {};
|
||||
if (opts.untrusted || session.untrusted) {
|
||||
extraSessionData.untrusted = true;
|
||||
|
||||
@@ -52,7 +52,7 @@ interface IMessage {
|
||||
*/
|
||||
class OlmEncryption extends EncryptionAlgorithm {
|
||||
private sessionPrepared = false;
|
||||
private prepPromise: Promise<void> = null;
|
||||
private prepPromise: Promise<void> | null = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -117,11 +117,11 @@ class OlmEncryption extends EncryptionAlgorithm {
|
||||
ciphertext: {},
|
||||
};
|
||||
|
||||
const promises = [];
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
for (let i = 0; i < users.length; ++i) {
|
||||
const userId = users[i];
|
||||
const devices = this.crypto.getStoredDevicesForUser(userId);
|
||||
const devices = this.crypto.getStoredDevicesForUser(userId) || [];
|
||||
|
||||
for (let j = 0; j < devices.length; ++j) {
|
||||
const deviceInfo = devices[j];
|
||||
@@ -240,7 +240,7 @@ class OlmDecryption extends DecryptionAlgorithm {
|
||||
throw new DecryptionError(
|
||||
"OLM_BAD_ROOM",
|
||||
"Message intended for room " + payload.room_id, {
|
||||
reported_room: event.getRoomId(),
|
||||
reported_room: event.getRoomId() || "ROOM_ID_UNDEFINED",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user