From 9efe294a79bdf50e6fd7ba1ffa0a32c23fb58d3b Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:29:24 +0200 Subject: [PATCH] fetch and verify keys on olm call signalling message --- src/matrix/DeviceMessageHandler.js | 62 ++++++++++++++---------------- src/matrix/Session.js | 11 +++++- src/matrix/Sync.js | 2 +- src/matrix/e2ee/README.md | 3 ++ 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/matrix/DeviceMessageHandler.js b/src/matrix/DeviceMessageHandler.js index c33236aa..507e07ef 100644 --- a/src/matrix/DeviceMessageHandler.js +++ b/src/matrix/DeviceMessageHandler.js @@ -38,7 +38,6 @@ export class DeviceMessageHandler { async prepareSync(toDeviceEvents, lock, txn, log) { log.set("messageTypes", countBy(toDeviceEvents, e => e.type)); - this._handleUnencryptedCallEvents(toDeviceEvents, log); const encryptedEvents = toDeviceEvents.filter(e => e.type === "m.room.encrypted"); if (!this._olmDecryption) { log.log("can't decrypt, encryption not enabled", log.level.Warn); @@ -54,20 +53,6 @@ export class DeviceMessageHandler { } const newRoomKeys = this._megolmDecryption.roomKeysFromDeviceMessages(olmDecryptChanges.results, log); - // const callMessages = olmDecryptChanges.results.filter(dr => this._callHandler.handlesDeviceMessageEventType(dr.event?.type)); - // // load devices by sender key - // await Promise.all(callMessages.map(async dr => { - // dr.setDevice(await this._getDevice(dr.senderCurve25519Key, txn)); - // })); - // // TODO: pass this in the prep and run it in afterSync or afterSyncComplete (as callHandler can send events as well)? - // for (const dr of callMessages) { - // if (dr.device) { - // this._callHandler.handleDeviceMessage(dr.event, dr.device.userId, dr.device.deviceId, log); - // } else { - // console.error("could not deliver message because don't have device for sender key", dr.event); - // } - // } - // TODO: somehow include rooms that received a call to_device message in the sync state? // or have updates flow through event emitter? // well, we don't really need to update the room other then when a call starts or stops @@ -76,33 +61,42 @@ export class DeviceMessageHandler { } } - _handleUnencryptedCallEvents(toDeviceEvents, log) { - const callMessages = toDeviceEvents.filter(e => this._callHandler.handlesDeviceMessageEventType(e.type)); - for (const event of callMessages) { - const userId = event.sender; - const deviceId = event.content.device_id; - this._callHandler.handleDeviceMessage(event, userId, deviceId, log); - } - } - /** check that prep is not undefined before calling this */ async writeSync(prep, txn) { // write olm changes prep.olmDecryptChanges.write(txn); const didWriteValues = await Promise.all(prep.newRoomKeys.map(key => this._megolmDecryption.writeRoomKey(key, txn))); - return didWriteValues.some(didWrite => !!didWrite); + const hasNewRoomKeys = didWriteValues.some(didWrite => !!didWrite); + return { + hasNewRoomKeys, + decryptionResults: prep.olmDecryptChanges.results + }; } - - async _getDevice(senderKey, txn) { - let device = this._senderDeviceCache.get(senderKey); - if (!device) { - device = await txn.deviceIdentities.getByCurve25519Key(senderKey); - if (device) { - this._senderDeviceCache.set(device); - } + async afterSyncCompleted(decryptionResults, deviceTracker, hsApi, log) { + // if we don't have a device, we need to fetch the device keys the message claims + // and check the keys, and we should only do network requests during + // sync processing in the afterSyncCompleted step. + const callMessages = decryptionResults.filter(dr => this._callHandler.handlesDeviceMessageEventType(dr.event?.type)); + if (callMessages.length) { + await log.wrap("process call signalling messages", async log => { + for (const dr of callMessages) { + // serialize device loading, so subsequent messages for the same device take advantage of the cache + const device = await deviceTracker.deviceForId(dr.event.sender, dr.event.content.device_id, hsApi, log); + dr.setDevice(device); + if (dr.isVerified) { + this._callHandler.handleDeviceMessage(dr.event, dr.userId, dr.deviceId, log); + } else { + log.log({ + l: "could not verify olm fingerprint key matches, ignoring", + ed25519Key: dr.device.ed25519Key, + claimedEd25519Key: dr.claimedEd25519Key, + deviceId, userId, + }); + } + } + }); } - return device; } } diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 26ab1702..c80cf527 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -685,7 +685,9 @@ export class Session { async writeSync(syncResponse, syncFilterId, preparation, txn, log) { const changes = { syncInfo: null, - e2eeAccountChanges: null + e2eeAccountChanges: null, + hasNewRoomKeys: false, + deviceMessageDecryptionResults: null, }; const syncToken = syncResponse.next_batch; if (syncToken !== this.syncToken) { @@ -706,7 +708,9 @@ export class Session { } if (preparation) { - changes.hasNewRoomKeys = await log.wrap("deviceMsgs", log => this._deviceMessageHandler.writeSync(preparation, txn, log)); + const {hasNewRoomKeys, decryptionResults} = await log.wrap("deviceMsgs", log => this._deviceMessageHandler.writeSync(preparation, txn, log)); + changes.hasNewRoomKeys = hasNewRoomKeys; + changes.deviceMessageDecryptionResults = decryptionResults; } // store account data @@ -747,6 +751,9 @@ export class Session { if (changes.hasNewRoomKeys) { this._keyBackup.get()?.flush(log); } + if (changes.deviceMessageDecryptionResults) { + await this._deviceMessageHandler.afterSyncCompleted(changes.deviceMessageDecryptionResults, this._deviceTracker, this._hsApi, log); + } } _tryReplaceRoomBeingCreated(roomId, log) { diff --git a/src/matrix/Sync.js b/src/matrix/Sync.js index 4f907563..f77fa79a 100644 --- a/src/matrix/Sync.js +++ b/src/matrix/Sync.js @@ -160,7 +160,7 @@ export class Sync { const isCatchupSync = this._status.get() === SyncStatus.CatchupSync; const sessionPromise = (async () => { try { - await log.wrap("session", log => this._session.afterSyncCompleted(sessionChanges, isCatchupSync, log), log.level.Detail); + await log.wrap("session", log => this._session.afterSyncCompleted(sessionChanges, isCatchupSync, log)); } catch (err) {} // error is logged, but don't fail sessionPromise })(); diff --git a/src/matrix/e2ee/README.md b/src/matrix/e2ee/README.md index fab53880..fdb4866c 100644 --- a/src/matrix/e2ee/README.md +++ b/src/matrix/e2ee/README.md @@ -41,5 +41,8 @@ Runs before any room.prepareSync, so the new room keys can be passed to each roo - e2ee account - generate more otks if needed - upload new otks if needed or device keys if not uploaded before + - device message handler: + - fetch keys we don't know about yet for (call) to_device messages identity + - pass signalling messages to call handler - rooms - share new room keys if needed