convert SessionDecryption to TS and adapt to use KeyLoader
This commit is contained in:
parent
b55930f084
commit
ac23119838
4 changed files with 106 additions and 93 deletions
|
@ -29,10 +29,10 @@ limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
export class DecryptionResult {
|
export class DecryptionResult {
|
||||||
constructor(event, senderCurve25519Key, claimedKeys) {
|
constructor(event, senderCurve25519Key, claimedEd25519Key) {
|
||||||
this.event = event;
|
this.event = event;
|
||||||
this.senderCurve25519Key = senderCurve25519Key;
|
this.senderCurve25519Key = senderCurve25519Key;
|
||||||
this.claimedEd25519Key = claimedKeys.ed25519;
|
this.claimedEd25519Key = claimedEd25519Key;
|
||||||
this._device = null;
|
this._device = null;
|
||||||
this._roomTracked = true;
|
this._roomTracked = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
103
src/matrix/e2ee/megolm/decryption/SessionDecryption.ts
Normal file
103
src/matrix/e2ee/megolm/decryption/SessionDecryption.ts
Normal file
|
@ -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<string, DecryptionResult>;
|
||||||
|
readonly errors?: Map<string, Error>;
|
||||||
|
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<DecryptAllResult> {
|
||||||
|
const replayEntries: ReplayDetectionEntry[] = [];
|
||||||
|
const results: Map<string, DecryptionResult> = new Map();
|
||||||
|
let errors: Map<string, Error> | 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -150,7 +150,7 @@ export class Decryption {
|
||||||
throw new DecryptionError("PLAINTEXT_NOT_JSON", event, {plaintext, error});
|
throw new DecryptionError("PLAINTEXT_NOT_JSON", event, {plaintext, error});
|
||||||
}
|
}
|
||||||
this._validatePayload(payload, event);
|
this._validatePayload(payload, event);
|
||||||
return new DecryptionResult(payload, senderKey, payload.keys);
|
return new DecryptionResult(payload, senderKey, payload.keys.ed25519);
|
||||||
} else {
|
} else {
|
||||||
throw new DecryptionError("OLM_NO_MATCHING_SESSION", event,
|
throw new DecryptionError("OLM_NO_MATCHING_SESSION", event,
|
||||||
{knownSessionIds: senderKeyDecryption.sessions.map(s => s.id)});
|
{knownSessionIds: senderKeyDecryption.sessions.map(s => s.id)});
|
||||||
|
|
Reference in a new issue