diff --git a/src/matrix/e2ee/DecryptionResult.js b/src/matrix/e2ee/DecryptionResult.js index c109e689..e1c2bcc4 100644 --- a/src/matrix/e2ee/DecryptionResult.js +++ b/src/matrix/e2ee/DecryptionResult.js @@ -29,10 +29,10 @@ limitations under the License. export class DecryptionResult { - constructor(event, senderCurve25519Key, claimedKeys) { + constructor(event, senderCurve25519Key, claimedEd25519Key) { this.event = event; this.senderCurve25519Key = senderCurve25519Key; - this.claimedEd25519Key = claimedKeys.ed25519; + this.claimedEd25519Key = claimedEd25519Key; this._device = null; this._roomTracked = true; } diff --git a/src/matrix/e2ee/megolm/decryption/SessionDecryption.js b/src/matrix/e2ee/megolm/decryption/SessionDecryption.js deleted file mode 100644 index 137ae9f8..00000000 --- a/src/matrix/e2ee/megolm/decryption/SessionDecryption.js +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright 2020 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. -*/ - -import {DecryptionResult} from "../../DecryptionResult.js"; -import {DecryptionError} from "../../common.js"; -import {ReplayDetectionEntry} from "./ReplayDetectionEntry.js"; - -/** - * Does the actual decryption of all events for a given megolm session in a batch - */ -export class SessionDecryption { - constructor(sessionInfo, events, olmWorker) { - sessionInfo.retain(); - this._sessionInfo = sessionInfo; - this._events = events; - this._olmWorker = olmWorker; - this._decryptionRequests = olmWorker ? [] : null; - } - - async decryptAll() { - const replayEntries = []; - const results = new Map(); - let errors; - const roomId = this._sessionInfo.roomId; - - await Promise.all(this._events.map(async event => { - try { - const {session} = this._sessionInfo; - const ciphertext = event.content.ciphertext; - let decryptionResult; - if (this._olmWorker) { - const request = this._olmWorker.megolmDecrypt(session, ciphertext); - this._decryptionRequests.push(request); - decryptionResult = await request.response(); - } else { - decryptionResult = session.decrypt(ciphertext); - } - const plaintext = decryptionResult.plaintext; - const messageIndex = decryptionResult.message_index; - let payload; - try { - payload = JSON.parse(plaintext); - } catch (err) { - throw new DecryptionError("PLAINTEXT_NOT_JSON", event, {plaintext, err}); - } - if (payload.room_id !== roomId) { - throw new DecryptionError("MEGOLM_WRONG_ROOM", event, - {encryptedRoomId: payload.room_id, eventRoomId: roomId}); - } - replayEntries.push(new ReplayDetectionEntry(session.session_id(), messageIndex, event)); - const result = new DecryptionResult(payload, this._sessionInfo.senderKey, this._sessionInfo.claimedKeys); - results.set(event.event_id, result); - } catch (err) { - // ignore AbortError from cancelling decryption requests in dispose method - if (err.name === "AbortError") { - return; - } - if (!errors) { - errors = new Map(); - } - errors.set(event.event_id, err); - } - })); - - return {results, errors, replayEntries}; - } - - dispose() { - if (this._decryptionRequests) { - for (const r of this._decryptionRequests) { - r.abort(); - } - } - // TODO: cancel decryptions here - this._sessionInfo.release(); - } -} diff --git a/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts b/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts new file mode 100644 index 00000000..3adf5bdb --- /dev/null +++ b/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts @@ -0,0 +1,103 @@ +/* +Copyright 2020 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. +*/ + +import {DecryptionResult} from "../../DecryptionResult.js"; +import {DecryptionError} from "../../common.js"; +import {ReplayDetectionEntry} from "./ReplayDetectionEntry"; +import type {IRoomKey} from "./RoomKey.js"; +import type {KeyLoader, OlmDecryptionResult} from "./KeyLoader"; +import type {OlmWorker} from "../../OlmWorker"; +import type {TimelineEvent} from "../../../storage/types"; + +interface DecryptAllResult { + readonly results: Map; + readonly errors?: Map; + readonly replayEntries: ReplayDetectionEntry[]; +} +/** + * Does the actual decryption of all events for a given megolm session in a batch + */ +export class SessionDecryption { + private key: IRoomKey; + private events: TimelineEvent[]; + private keyLoader: KeyLoader; + private olmWorker?: OlmWorker; + private decryptionRequests?: any[]; + + constructor(key: IRoomKey, events: TimelineEvent[], olmWorker: OlmWorker | undefined, keyLoader: KeyLoader) { + this.key = key; + this.events = events; + this.olmWorker = olmWorker; + this.keyLoader = keyLoader; + this.decryptionRequests = olmWorker ? [] : undefined; + } + + async decryptAll(): Promise { + const replayEntries: ReplayDetectionEntry[] = []; + const results: Map = new Map(); + let errors: Map | undefined; + + await this.keyLoader.useKey(this.key, async session => { + for (const event of this.events) { + try { + const ciphertext = event.content.ciphertext as string; + let decryptionResult: OlmDecryptionResult | undefined; + // TODO: pass all cipthertexts in one go to the megolm worker and don't deserialize the key until in the worker? + if (this.olmWorker) { + const request = this.olmWorker.megolmDecrypt(session, ciphertext); + this.decryptionRequests!.push(request); + decryptionResult = await request.response(); + } else { + decryptionResult = session.decrypt(ciphertext); + } + const {plaintext} = decryptionResult!; + let payload; + try { + payload = JSON.parse(plaintext); + } catch (err) { + throw new DecryptionError("PLAINTEXT_NOT_JSON", event, {plaintext, err}); + } + if (payload.room_id !== this.key.roomId) { + throw new DecryptionError("MEGOLM_WRONG_ROOM", event, + {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); + results.set(event.event_id, result); + } catch (err) { + // ignore AbortError from cancelling decryption requests in dispose method + if (err.name === "AbortError") { + return; + } + if (!errors) { + errors = new Map(); + } + errors.set(event.event_id, err); + } + } + }); + + return {results, errors, replayEntries}; + } + + dispose() { + if (this.decryptionRequests) { + for (const r of this.decryptionRequests) { + r.abort(); + } + } + } +} diff --git a/src/matrix/e2ee/olm/Decryption.js b/src/matrix/e2ee/olm/Decryption.js index c3777bd3..0af3bd23 100644 --- a/src/matrix/e2ee/olm/Decryption.js +++ b/src/matrix/e2ee/olm/Decryption.js @@ -150,7 +150,7 @@ export class Decryption { throw new DecryptionError("PLAINTEXT_NOT_JSON", event, {plaintext, error}); } this._validatePayload(payload, event); - return new DecryptionResult(payload, senderKey, payload.keys); + return new DecryptionResult(payload, senderKey, payload.keys.ed25519); } else { throw new DecryptionError("OLM_NO_MATCHING_SESSION", event, {knownSessionIds: senderKeyDecryption.sessions.map(s => s.id)});