From c544819b42fe0f85235985a89605f1c091141bc5 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 28 Oct 2022 15:33:46 +0200 Subject: [PATCH 01/15] rename and refactor method to verify locally known senders a bit to differentiate with method to come for verifying senders that need to be fetched still, and also allow reuse of verification logic by said new method. Also get rid of roomTracked flag in DecryptionResult as once we fetch unknown senders (in commit to come), we should now always have a device, unless the device isn't known on the server, in which case we should shout. --- src/matrix/e2ee/DecryptionResult.ts | 9 +-------- src/matrix/e2ee/RoomEncryption.js | 31 ++++++++++++++++------------- src/matrix/room/BaseRoom.js | 2 +- src/matrix/room/Room.js | 2 +- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/matrix/e2ee/DecryptionResult.ts b/src/matrix/e2ee/DecryptionResult.ts index 7735856a..0bb2e926 100644 --- a/src/matrix/e2ee/DecryptionResult.ts +++ b/src/matrix/e2ee/DecryptionResult.ts @@ -47,10 +47,6 @@ export class DecryptionResult { this.device = device; } - setRoomNotTrackedYet(): void { - this.roomTracked = false; - } - get isVerified(): boolean { if (this.device) { const comesFromDevice = this.device.ed25519Key === this.claimedEd25519Key; @@ -62,15 +58,12 @@ export class DecryptionResult { get isUnverified(): boolean { if (this.device) { return !this.isVerified; - } else if (this.isVerificationUnknown) { - return false; } else { return true; } } get isVerificationUnknown(): boolean { - // verification is unknown if we haven't yet fetched the devices for the room - return !this.device && !this.roomTracked; + return !this.device; } } diff --git a/src/matrix/e2ee/RoomEncryption.js b/src/matrix/e2ee/RoomEncryption.js index 36424b02..dbd8fd43 100644 --- a/src/matrix/e2ee/RoomEncryption.js +++ b/src/matrix/e2ee/RoomEncryption.js @@ -208,16 +208,19 @@ export class RoomEncryption { }); } - async _verifyDecryptionResult(result, txn) { - let device = this._senderDeviceCache.get(result.senderCurve25519Key); - if (!device) { - device = await this._deviceTracker.getDeviceByCurve25519Key(result.senderCurve25519Key, txn); - this._senderDeviceCache.set(result.senderCurve25519Key, device); - } - if (device) { - result.setDevice(device); - } else if (!this._room.isTrackingMembers) { - result.setRoomNotTrackedYet(); + async _verifyDecryptionResults(results, txn) { + await Promise.all(results.map(async result => { + let device = this._senderDeviceCache.get(result.senderCurve25519Key); + if (!device) { + device = await this._deviceTracker.getDeviceByCurve25519Key(result.senderCurve25519Key, txn); + this._senderDeviceCache.set(result.senderCurve25519Key, device); + } + if (device) { + result.setDevice(device); + } + })); + } + } } @@ -545,10 +548,10 @@ class BatchDecryptionResult { } } - verifySenders(txn) { - return Promise.all(Array.from(this.results.values()).map(result => { - return this._roomEncryption._verifyDecryptionResult(result, txn); - })); + /** Verify the decryption results by looking for the corresponding device in local persistance + * @returns {BatchDecryptionResult} a new batch result with the results for which we now found a device */ + verifyKnownSenders(txn) { + return this._roomEncryption._verifyDecryptionResults(Array.from(this.results.values()), txn); } } diff --git a/src/matrix/room/BaseRoom.js b/src/matrix/room/BaseRoom.js index 57d2a7b2..6f14b7f9 100644 --- a/src/matrix/room/BaseRoom.js +++ b/src/matrix/room/BaseRoom.js @@ -154,7 +154,7 @@ export class BaseRoom extends EventEmitter { try { decryption = await changes.write(writeTxn, log); if (isTimelineOpen) { - await decryption.verifySenders(writeTxn); + await decryption.verifyKnownSenders(writeTxn); } } catch (err) { writeTxn.abort(); diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index 8cc87845..2ff17d42 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -129,7 +129,7 @@ export class Room extends BaseRoom { log.set("decryptionResults", decryption.results.size); log.set("decryptionErrors", decryption.errors.size); if (this._isTimelineOpen) { - await decryption.verifySenders(txn); + await decryption.verifyKnownSenders(txn); } decryption.applyToEntries(newEntries); if (retryEntries?.length) { From 9c13b2b4a43b71311261d820e933bcd995590458 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 28 Oct 2022 15:41:18 +0200 Subject: [PATCH 02/15] add method to fetch missing sender keys --- src/matrix/e2ee/DecryptionResult.ts | 5 ++-- src/matrix/e2ee/RoomEncryption.js | 30 +++++++++++++++++++ .../megolm/decryption/SessionDecryption.ts | 2 +- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/matrix/e2ee/DecryptionResult.ts b/src/matrix/e2ee/DecryptionResult.ts index 0bb2e926..8846616c 100644 --- a/src/matrix/e2ee/DecryptionResult.ts +++ b/src/matrix/e2ee/DecryptionResult.ts @@ -27,6 +27,7 @@ limitations under the License. */ import type {DeviceIdentity} from "../storage/idb/stores/DeviceIdentityStore"; +import type {TimelineEvent} from "../storage/types"; type DecryptedEvent = { type?: string, @@ -35,12 +36,12 @@ type DecryptedEvent = { export class DecryptionResult { private device?: DeviceIdentity; - private roomTracked: boolean = true; constructor( public readonly event: DecryptedEvent, public readonly senderCurve25519Key: string, - public readonly claimedEd25519Key: string + public readonly claimedEd25519Key: string, + public readonly encryptedEvent?: TimelineEvent ) {} setDevice(device: DeviceIdentity): void { diff --git a/src/matrix/e2ee/RoomEncryption.js b/src/matrix/e2ee/RoomEncryption.js index dbd8fd43..0e17ad6f 100644 --- a/src/matrix/e2ee/RoomEncryption.js +++ b/src/matrix/e2ee/RoomEncryption.js @@ -221,7 +221,30 @@ export class RoomEncryption { })); } + /** fetches the devices that are not yet known locally from the homeserver to verify the sender of this message. */ + _fetchKeyAndVerifyDecryptionResults(results, hsApi, log) { + const resultsWithoutDevice = results.filter(r => r.isVerificationUnknown); + if (resultsWithoutDevice.length) { + return log.wrap("fetch unverified senders", async log => { + const sendersWithoutDevice = Array.from(resultsWithoutDevice.reduce((senders, r) => { + return senders.add(r.encryptedEvent.sender); + }, new Set())); + log.set("senders", sendersWithoutDevice); + // fetch the devices, ignore return value, + // and just reuse _verifyDecryptionResults method so we only have one impl how to verify + await this._deviceTracker.devicesForRoomMembers(this._room.id, sendersWithoutDevice, hsApi, log); + // now that we've fetched the missing devices, try verifying the results again + const txn = await this._storage.readTxn([this._storage.storeNames.deviceIdentities]); + return this._verifyDecryptionResults(resultsWithoutDevice, txn); + const resultsWithFoundDevice = resultsWithoutDevice.filter(r => !r.isVerificationUnknown); + const resultsToEventIdMap = resultsWithFoundDevice.reduce((map, r) => { + map.set(r.encryptedEvent.event_id, r); + return map; + }, new Map()); + return new BatchDecryptionResult(resultsToEventIdMap, new Map(), this); + }); } + return new BatchDecryptionResult(new Map(), new Map(), this); } async _requestMissingSessionFromBackup(senderKey, sessionId, log) { @@ -553,6 +576,13 @@ class BatchDecryptionResult { verifyKnownSenders(txn) { return this._roomEncryption._verifyDecryptionResults(Array.from(this.results.values()), txn); } + + /** Verify any decryption results for which we could not find a device when + * calling `verifyKnownSenders` prior, by fetching them from the homeserver. + * @returns {Promise} the results for which we found a device */ + fetchAndVerifyRemainingSenders(hsApi, log) { + return this._roomEncryption._fetchKeyAndVerifyDecryptionResults(Array.from(this.results.values()), hsApi, log); + } } import {createMockStorage} from "../../mocks/Storage"; diff --git a/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts b/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts index 57ef9a96..36224fc5 100644 --- a/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts +++ b/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts @@ -75,7 +75,7 @@ export class SessionDecryption { {encryptedRoomId: payload.room_id, eventRoomId: this.key.roomId}); } replayEntries.push(new ReplayDetectionEntry(this.key.sessionId, decryptionResult!.message_index, event)); - const result = new DecryptionResult(payload, this.key.senderKey, this.key.claimedEd25519Key); + const result = new DecryptionResult(payload, this.key.senderKey, this.key.claimedEd25519Key, event); results.set(event.event_id, result); } catch (err) { // ignore AbortError from cancelling decryption requests in dispose method From fcb1546fba74c711d4299b30af62f4da11c05f0f Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:25:31 +0200 Subject: [PATCH 03/15] fetch keys as part of afterSyncCompleted step remove needsAfterSyncCompleted step as well, performance difference should be neglible --- src/matrix/Sync.js | 14 ++++--------- src/matrix/e2ee/RoomEncryption.js | 13 +++++++++++- src/matrix/room/Room.js | 33 +++++++++++++++++++++++-------- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/matrix/Sync.js b/src/matrix/Sync.js index 3574213e..c995e3e2 100644 --- a/src/matrix/Sync.js +++ b/src/matrix/Sync.js @@ -47,10 +47,8 @@ function timelineIsEmpty(roomResponse) { * const changes = await room.writeSync(roomResponse, isInitialSync, preparation, syncTxn); * // applies and emits changes once syncTxn is committed * room.afterSync(changes); - * if (room.needsAfterSyncCompleted(changes)) { - * // can do network requests - * await room.afterSyncCompleted(changes); - * } + * // can do network requests + * await room.afterSyncCompleted(changes); * ``` */ export class Sync { @@ -163,13 +161,9 @@ export class Sync { await log.wrap("session", log => this._session.afterSyncCompleted(sessionChanges, isCatchupSync, log), log.level.Detail); } catch (err) {} // error is logged, but don't fail sessionPromise })(); - - const roomsNeedingAfterSyncCompleted = roomStates.filter(rs => { - return rs.room.needsAfterSyncCompleted(rs.changes); - }); - const roomsPromises = roomsNeedingAfterSyncCompleted.map(async rs => { + const roomsPromises = roomStates.map(async rs => { try { - await log.wrap("room", log => rs.room.afterSyncCompleted(rs.changes, log), log.level.Detail); + await rs.room.afterSyncCompleted(rs.changes, log); } catch (err) {} // error is logged, but don't fail roomsPromises }); // run everything in parallel, diff --git a/src/matrix/e2ee/RoomEncryption.js b/src/matrix/e2ee/RoomEncryption.js index 0e17ad6f..080aa2eb 100644 --- a/src/matrix/e2ee/RoomEncryption.js +++ b/src/matrix/e2ee/RoomEncryption.js @@ -557,15 +557,17 @@ class BatchDecryptionResult { this._roomEncryption = roomEncryption; } - applyToEntries(entries) { + applyToEntries(entries, callback = undefined) { for (const entry of entries) { const result = this.results.get(entry.id); if (result) { entry.setDecryptionResult(result); + callback?.(entry); } else { const error = this.errors.get(entry.id); if (error) { entry.setDecryptionError(error); + callback?.(entry); } } } @@ -577,6 +579,15 @@ class BatchDecryptionResult { return this._roomEncryption._verifyDecryptionResults(Array.from(this.results.values()), txn); } + get hasUnverifiedSenders() { + for (const r of this.results.values()) { + if (r.isVerificationUnknown) { + return true; + } + } + return false; + } + /** Verify any decryption results for which we could not find a device when * calling `verifyKnownSenders` prior, by fetching them from the homeserver. * @returns {Promise} the results for which we found a device */ diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index 2ff17d42..ad8ec110 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -189,6 +189,7 @@ export class Room extends BaseRoom { heroChanges, powerLevelsEvent, encryptionChanges, + decryption }; } @@ -291,19 +292,35 @@ export class Room extends BaseRoom { } } - needsAfterSyncCompleted({encryptionChanges}) { - return encryptionChanges?.shouldFlush; - } - /** * Only called if the result of writeSync had `needsAfterSyncCompleted` set. * Can be used to do longer running operations that resulted from the last sync, * like network operations. */ - async afterSyncCompleted(changes, log) { - log.set("id", this.id); - if (this._roomEncryption) { - await this._roomEncryption.flushPendingRoomKeyShares(this._hsApi, null, log); + async afterSyncCompleted({encryptionChanges, decryption, newEntries, updatedEntries}, log) { + const shouldFlushKeys = encryptionChanges?.shouldFlush; + const shouldFetchUnverifiedSenders = this._isTimelineOpen && decryption?.hasUnverifiedSenders; + // only log rooms where we actually do something + if (shouldFlushKeys || shouldFetchUnverifiedSenders) { + log.wrap({l: "room", id: this.id}, async log => { + const promises = []; + if (shouldFlushKeys) { + promises.push(this._roomEncryption.flushPendingRoomKeyShares(this._hsApi, null, log)); + } + if (shouldFetchUnverifiedSenders) { + const promise = (async () => { + const newlyVerifiedDecryption = await decryption.fetchAndVerifyRemainingSenders(this._hsApi, log); + const verifiedEntries = []; + const updateCallback = entry => verifiedEntries.push(entry); + newlyVerifiedDecryption.applyToEntries(newEntries, updateCallback); + newlyVerifiedDecryption.applyToEntries(updated, updateCallback); + // TODO: update _observedEvents here as well? + this._timeline.replaceEntries(verifiedEntries); + }); + promises.push(promise); + } + await Promise.all(promises); + }); } } From 6dbcd46d80a56a4cda2f9f62472f10d6f82f9db9 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:31:00 +0200 Subject: [PATCH 04/15] cleanup of ctor --- .../e2ee/megolm/decryption/SessionDecryption.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts b/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts index 36224fc5..ca294460 100644 --- a/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts +++ b/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts @@ -31,17 +31,14 @@ interface DecryptAllResult { * Does the actual decryption of all events for a given megolm session in a batch */ export class SessionDecryption { - private key: RoomKey; - private events: TimelineEvent[]; - private keyLoader: KeyLoader; - private olmWorker?: OlmWorker; private decryptionRequests?: any[]; - constructor(key: RoomKey, events: TimelineEvent[], olmWorker: OlmWorker | undefined, keyLoader: KeyLoader) { - this.key = key; - this.events = events; - this.olmWorker = olmWorker; - this.keyLoader = keyLoader; + constructor( + private readonly key: RoomKey, + private readonly events: TimelineEvent[], + private readonly olmWorker: OlmWorker | undefined, + private readonly keyLoader: KeyLoader + ) { this.decryptionRequests = olmWorker ? [] : undefined; } From d0122d17c0ceffe4cc5d4a0938d3e968ad83537e Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:49:33 +0200 Subject: [PATCH 05/15] don't assume timeline is open as have an await since last check --- src/matrix/room/Room.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index ad8ec110..90a6ec26 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -315,7 +315,7 @@ export class Room extends BaseRoom { newlyVerifiedDecryption.applyToEntries(newEntries, updateCallback); newlyVerifiedDecryption.applyToEntries(updated, updateCallback); // TODO: update _observedEvents here as well? - this._timeline.replaceEntries(verifiedEntries); + this._timeline?.replaceEntries(verifiedEntries); }); promises.push(promise); } From 218d25d973e69a3b2bbca61ccdc5c50606021750 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:50:01 +0200 Subject: [PATCH 06/15] fix typo --- src/matrix/room/Room.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index 90a6ec26..e03f4cd1 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -313,7 +313,7 @@ export class Room extends BaseRoom { const verifiedEntries = []; const updateCallback = entry => verifiedEntries.push(entry); newlyVerifiedDecryption.applyToEntries(newEntries, updateCallback); - newlyVerifiedDecryption.applyToEntries(updated, updateCallback); + newlyVerifiedDecryption.applyToEntries(updatedEntries, updateCallback); // TODO: update _observedEvents here as well? this._timeline?.replaceEntries(verifiedEntries); }); From dc25f96e73d3a1aa11eb455e50ddc999826a9f05 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:55:20 +0200 Subject: [PATCH 07/15] update observed events as well --- src/matrix/room/Room.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index e03f4cd1..ab336ea1 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -314,8 +314,8 @@ export class Room extends BaseRoom { const updateCallback = entry => verifiedEntries.push(entry); newlyVerifiedDecryption.applyToEntries(newEntries, updateCallback); newlyVerifiedDecryption.applyToEntries(updatedEntries, updateCallback); - // TODO: update _observedEvents here as well? this._timeline?.replaceEntries(verifiedEntries); + this._observedEvents?.updateEvents(verifiedEntries); }); promises.push(promise); } From 6123d794da31302c5b2382446ac321c7a1e78f90 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:55:36 +0200 Subject: [PATCH 08/15] also fetch unknown sender keys after decrypting entries outside of sync --- src/matrix/room/BaseRoom.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/matrix/room/BaseRoom.js b/src/matrix/room/BaseRoom.js index 6f14b7f9..851e75c9 100644 --- a/src/matrix/room/BaseRoom.js +++ b/src/matrix/room/BaseRoom.js @@ -166,6 +166,16 @@ export class BaseRoom extends EventEmitter { if (this._observedEvents) { this._observedEvents.updateEvents(entries); } + if (isTimelineOpen && decryption.hasUnverifiedSenders) { + // verify missing senders async and update timeline once done so we don't delay rendering with network requests + log.wrapDetached("fetch unknown senders keys", async () => { + const newlyVerifiedDecryption = await decryption.fetchAndVerifyRemainingSenders(this._hsApi, log); + const verifiedEntries = []; + newlyVerifiedDecryption.applyToEntries(entries, entry => verifiedEntries.push(entry)); + this._timeline?.replaceEntries(verifiedEntries); + this._observedEvents?.updateEvents(verifiedEntries); + }); + } }, ensureLogItem(log)); return request; } From cd5343414ab0003999972a54d50617d6d4c0f418 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 28 Oct 2022 17:16:32 +0200 Subject: [PATCH 09/15] fix lint --- src/matrix/e2ee/RoomEncryption.js | 5 ++--- src/matrix/room/Room.js | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/matrix/e2ee/RoomEncryption.js b/src/matrix/e2ee/RoomEncryption.js index 080aa2eb..6d42c873 100644 --- a/src/matrix/e2ee/RoomEncryption.js +++ b/src/matrix/e2ee/RoomEncryption.js @@ -235,7 +235,7 @@ export class RoomEncryption { await this._deviceTracker.devicesForRoomMembers(this._room.id, sendersWithoutDevice, hsApi, log); // now that we've fetched the missing devices, try verifying the results again const txn = await this._storage.readTxn([this._storage.storeNames.deviceIdentities]); - return this._verifyDecryptionResults(resultsWithoutDevice, txn); + await this._verifyDecryptionResults(resultsWithoutDevice, txn); const resultsWithFoundDevice = resultsWithoutDevice.filter(r => !r.isVerificationUnknown); const resultsToEventIdMap = resultsWithFoundDevice.reduce((map, r) => { map.set(r.encryptedEvent.event_id, r); @@ -600,7 +600,6 @@ import {createMockStorage} from "../../mocks/Storage"; import {Clock as MockClock} from "../../mocks/Clock"; import {poll} from "../../mocks/poll"; import {Instance as NullLoggerInstance} from "../../logging/NullLogger"; -import {ConsoleLogger} from "../../logging/ConsoleLogger"; import {HomeServer as MockHomeServer} from "../../mocks/HomeServer.js"; export function tests() { @@ -709,7 +708,7 @@ export function tests() { const storage = await createMockStorage(); let isMemberChangesCalled = false; const deviceTracker = { - async writeMemberChanges(room, memberChanges, historyVisibility, txn) { + async writeMemberChanges(room, memberChanges, historyVisibility) { assert.equal(historyVisibility, "invited"); isMemberChangesCalled = true; return {removed: [], added: []}; diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index ab336ea1..aad0310a 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -124,8 +124,9 @@ export class Room extends BaseRoom { const {entries: newEntries, updatedEntries, newLiveKey, memberChanges} = await log.wrap("syncWriter", log => this._syncWriter.writeSync( roomResponse, isRejoin, summaryChanges.hasFetchedMembers, txn, log), log.level.Detail); + let decryption; if (decryptChanges) { - const decryption = await log.wrap("decryptChanges", log => decryptChanges.write(txn, log)); + decryption = await log.wrap("decryptChanges", log => decryptChanges.write(txn, log)); log.set("decryptionResults", decryption.results.size); log.set("decryptionErrors", decryption.errors.size); if (this._isTimelineOpen) { From 25c8f1cf04beacd7db56d44afb7bd9a8dbd176bf Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:36:31 +0100 Subject: [PATCH 10/15] always return a promise from this method, even if nothing to verify --- src/matrix/e2ee/RoomEncryption.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/e2ee/RoomEncryption.js b/src/matrix/e2ee/RoomEncryption.js index 6d42c873..4dd6cd0b 100644 --- a/src/matrix/e2ee/RoomEncryption.js +++ b/src/matrix/e2ee/RoomEncryption.js @@ -222,7 +222,7 @@ export class RoomEncryption { } /** fetches the devices that are not yet known locally from the homeserver to verify the sender of this message. */ - _fetchKeyAndVerifyDecryptionResults(results, hsApi, log) { + async _fetchKeyAndVerifyDecryptionResults(results, hsApi, log) { const resultsWithoutDevice = results.filter(r => r.isVerificationUnknown); if (resultsWithoutDevice.length) { return log.wrap("fetch unverified senders", async log => { From 258260024989013523b81f0f8e98480d2af6e319 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:37:20 +0100 Subject: [PATCH 11/15] await operation, otherwise it keeps running during next sync --- src/matrix/room/Room.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index aad0310a..fab3bf15 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -303,7 +303,7 @@ export class Room extends BaseRoom { const shouldFetchUnverifiedSenders = this._isTimelineOpen && decryption?.hasUnverifiedSenders; // only log rooms where we actually do something if (shouldFlushKeys || shouldFetchUnverifiedSenders) { - log.wrap({l: "room", id: this.id}, async log => { + await log.wrap({l: "room", id: this.id}, async log => { const promises = []; if (shouldFlushKeys) { promises.push(this._roomEncryption.flushPendingRoomKeyShares(this._hsApi, null, log)); From dfede83c0bc194be7ca3327f6552ec07d51ea7f2 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:37:38 +0100 Subject: [PATCH 12/15] log verifying senders in own item --- src/matrix/room/Room.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index fab3bf15..d3d1a3b0 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -309,15 +309,16 @@ export class Room extends BaseRoom { promises.push(this._roomEncryption.flushPendingRoomKeyShares(this._hsApi, null, log)); } if (shouldFetchUnverifiedSenders) { - const promise = (async () => { + const promise = log.wrap("verify senders", (async log => { const newlyVerifiedDecryption = await decryption.fetchAndVerifyRemainingSenders(this._hsApi, log); const verifiedEntries = []; const updateCallback = entry => verifiedEntries.push(entry); newlyVerifiedDecryption.applyToEntries(newEntries, updateCallback); newlyVerifiedDecryption.applyToEntries(updatedEntries, updateCallback); + log.set("verifiedEntries", verifiedEntries.length); this._timeline?.replaceEntries(verifiedEntries); this._observedEvents?.updateEvents(verifiedEntries); - }); + })); promises.push(promise); } await Promise.all(promises); From dd7bbe89ac6bc4abf13d98f73afc480912126fe4 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:37:53 +0100 Subject: [PATCH 13/15] put detached logs in correct log item --- src/matrix/room/BaseRoom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/room/BaseRoom.js b/src/matrix/room/BaseRoom.js index 851e75c9..9e12f257 100644 --- a/src/matrix/room/BaseRoom.js +++ b/src/matrix/room/BaseRoom.js @@ -168,7 +168,7 @@ export class BaseRoom extends EventEmitter { } if (isTimelineOpen && decryption.hasUnverifiedSenders) { // verify missing senders async and update timeline once done so we don't delay rendering with network requests - log.wrapDetached("fetch unknown senders keys", async () => { + log.wrapDetached("fetch unknown senders keys", async log => { const newlyVerifiedDecryption = await decryption.fetchAndVerifyRemainingSenders(this._hsApi, log); const verifiedEntries = []; newlyVerifiedDecryption.applyToEntries(entries, entry => verifiedEntries.push(entry)); From fa21ac021c014c8d0e0b9096f3bf1932cb78e9de Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:38:08 +0100 Subject: [PATCH 14/15] use binding to update marking a message as (un)verified --- src/platform/web/ui/session/room/timeline/BaseMessageView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/session/room/timeline/BaseMessageView.js b/src/platform/web/ui/session/room/timeline/BaseMessageView.js index 74b96ecf..ee0a37db 100644 --- a/src/platform/web/ui/session/room/timeline/BaseMessageView.js +++ b/src/platform/web/ui/session/room/timeline/BaseMessageView.js @@ -46,7 +46,7 @@ export class BaseMessageView extends TemplateView { "Timeline_message": true, own: vm.isOwn, unsent: vm.isUnsent, - unverified: vm.isUnverified, + unverified: vm => vm.isUnverified, disabled: !this._interactive, continuation: vm => vm.isContinuation, }, From 6c73c317351f3e28c914b115c60f7d385346323d Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 3 Nov 2022 17:32:01 +0100 Subject: [PATCH 15/15] take any decryption result, as it might now have a device to verify with --- src/matrix/room/timeline/entries/EventEntry.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/matrix/room/timeline/entries/EventEntry.js b/src/matrix/room/timeline/entries/EventEntry.js index d218a598..cf56cbf9 100644 --- a/src/matrix/room/timeline/entries/EventEntry.js +++ b/src/matrix/room/timeline/entries/EventEntry.js @@ -33,10 +33,11 @@ export class EventEntry extends BaseEventEntry { } updateFrom(other) { - if (other._decryptionResult && !this._decryptionResult) { + // only update these when we attempted decryption, as some updates (like reactions) don't. + if (other._decryptionResult) { this._decryptionResult = other._decryptionResult; } - if (other._decryptionError && !this._decryptionError) { + if (other._decryptionError) { this._decryptionError = other._decryptionError; } this._contextForEntries = other.contextForEntries;