2020-09-02 17:54:38 +05:30
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2020-09-04 15:36:26 +05:30
|
|
|
import {DecryptionError} from "../common.js";
|
2020-09-09 19:58:43 +05:30
|
|
|
import {DecryptionPreparation} from "./decryption/DecryptionPreparation.js";
|
2021-10-22 21:16:39 +05:30
|
|
|
import {SessionDecryption} from "./decryption/SessionDecryption";
|
2021-03-01 19:34:45 +05:30
|
|
|
import {MEGOLM_ALGORITHM} from "../common.js";
|
2021-10-22 21:16:39 +05:30
|
|
|
import {validateEvent, groupEventsBySession} from "./decryption/utils";
|
|
|
|
import {keyFromStorage, keyFromDeviceMessage, keyFromBackup} from "./decryption/RoomKey";
|
|
|
|
import type {IRoomKey, IIncomingRoomKey} from "./decryption/RoomKey";
|
|
|
|
import type {KeyLoader} from "./decryption/KeyLoader";
|
|
|
|
import type {OlmWorker} from "../OlmWorker";
|
|
|
|
import type {Transaction} from "../../storage/idb/Transaction";
|
|
|
|
import type {TimelineEvent} from "../../storage/types";
|
|
|
|
import type {DecryptionResult} from "../DecryptionResult";
|
|
|
|
import type {LogItem} from "../../../logging/LogItem";
|
2020-09-04 15:36:26 +05:30
|
|
|
|
2020-09-02 17:54:38 +05:30
|
|
|
export class Decryption {
|
2021-10-22 21:16:39 +05:30
|
|
|
private keyLoader: KeyLoader;
|
|
|
|
private olmWorker?: OlmWorker;
|
2020-09-02 17:54:38 +05:30
|
|
|
|
2021-10-22 21:16:39 +05:30
|
|
|
constructor(keyLoader: KeyLoader, olmWorker: OlmWorker | undefined) {
|
|
|
|
this.keyLoader = keyLoader;
|
|
|
|
this.olmWorker = olmWorker;
|
2020-09-04 15:36:26 +05:30
|
|
|
}
|
|
|
|
|
2021-03-02 19:59:35 +05:30
|
|
|
async addMissingKeyEventIds(roomId, senderKey, sessionId, eventIds, txn) {
|
|
|
|
let sessionEntry = await txn.inboundGroupSessions.get(roomId, senderKey, sessionId);
|
|
|
|
// we never want to overwrite an existing key
|
|
|
|
if (sessionEntry?.session) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (sessionEntry) {
|
|
|
|
const uniqueEventIds = new Set(sessionEntry.eventIds);
|
|
|
|
for (const id of eventIds) {
|
|
|
|
uniqueEventIds.add(id);
|
|
|
|
}
|
|
|
|
sessionEntry.eventIds = Array.from(uniqueEventIds);
|
|
|
|
} else {
|
|
|
|
sessionEntry = {roomId, senderKey, sessionId, eventIds};
|
|
|
|
}
|
|
|
|
txn.inboundGroupSessions.set(sessionEntry);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getEventIdsForMissingKey(roomId, senderKey, sessionId, txn) {
|
|
|
|
const sessionEntry = await txn.inboundGroupSessions.get(roomId, senderKey, sessionId);
|
|
|
|
if (sessionEntry && !sessionEntry.session) {
|
|
|
|
return sessionEntry.eventIds;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async hasSession(roomId, senderKey, sessionId, txn) {
|
|
|
|
const sessionEntry = await txn.inboundGroupSessions.get(roomId, senderKey, sessionId);
|
|
|
|
const isValidSession = typeof sessionEntry?.session === "string";
|
|
|
|
return isValidSession;
|
|
|
|
}
|
|
|
|
|
2020-09-08 14:18:11 +05:30
|
|
|
/**
|
2020-09-09 19:58:43 +05:30
|
|
|
* Reads all the state from storage to be able to decrypt the given events.
|
|
|
|
* Decryption can then happen outside of a storage transaction.
|
2020-09-08 14:18:11 +05:30
|
|
|
* @param {[type]} roomId [description]
|
2021-03-01 19:34:45 +05:30
|
|
|
* @param {[type]} events [description]
|
|
|
|
* @param {RoomKey[]?} newKeys keys as returned from extractRoomKeys, but not yet committed to storage. May be undefined.
|
2020-09-08 14:18:11 +05:30
|
|
|
* @param {[type]} sessionCache [description]
|
|
|
|
* @param {[type]} txn [description]
|
2020-09-09 19:58:43 +05:30
|
|
|
* @return {DecryptionPreparation}
|
2020-09-08 14:18:11 +05:30
|
|
|
*/
|
2021-10-22 21:16:39 +05:30
|
|
|
async prepareDecryptAll(roomId: string, events: TimelineEvent[], newKeys: IIncomingRoomKey[] | undefined, txn: Transaction) {
|
2020-09-09 19:58:43 +05:30
|
|
|
const errors = new Map();
|
2021-10-22 21:16:39 +05:30
|
|
|
const validEvents: TimelineEvent[] = [];
|
2020-09-09 19:58:43 +05:30
|
|
|
|
|
|
|
for (const event of events) {
|
2021-03-02 19:59:35 +05:30
|
|
|
if (validateEvent(event)) {
|
2020-09-09 19:58:43 +05:30
|
|
|
validEvents.push(event);
|
|
|
|
} else {
|
|
|
|
errors.set(event.event_id, new DecryptionError("MEGOLM_INVALID_EVENT", event))
|
|
|
|
}
|
2020-09-04 15:36:26 +05:30
|
|
|
}
|
|
|
|
|
2021-03-02 19:59:35 +05:30
|
|
|
const eventsBySession = groupEventsBySession(validEvents);
|
2020-09-09 19:58:43 +05:30
|
|
|
|
2021-10-22 21:16:39 +05:30
|
|
|
const sessionDecryptions: SessionDecryption[] = [];
|
2021-03-02 19:59:35 +05:30
|
|
|
await Promise.all(Array.from(eventsBySession.values()).map(async group => {
|
2021-10-22 21:16:39 +05:30
|
|
|
const key = await this.getRoomKey(roomId, group.senderKey!, group.sessionId!, newKeys, txn);
|
|
|
|
if (key) {
|
|
|
|
sessionDecryptions.push(new SessionDecryption(key, group.events, this.olmWorker, this.keyLoader));
|
2021-03-01 19:34:45 +05:30
|
|
|
} else {
|
2021-03-02 19:59:35 +05:30
|
|
|
for (const event of group.events) {
|
2020-09-09 19:58:43 +05:30
|
|
|
errors.set(event.event_id, new DecryptionError("MEGOLM_NO_SESSION", event));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
return new DecryptionPreparation(roomId, sessionDecryptions, errors);
|
|
|
|
}
|
|
|
|
|
2021-10-22 21:16:39 +05:30
|
|
|
private async getRoomKey(roomId: string, senderKey: string, sessionId: string, newKeys: IIncomingRoomKey[] | undefined, txn: Transaction): Promise<IRoomKey | undefined> {
|
2021-03-01 19:34:45 +05:30
|
|
|
if (newKeys) {
|
|
|
|
const key = newKeys.find(k => k.roomId === roomId && k.senderKey === senderKey && k.sessionId === sessionId);
|
2021-10-22 21:16:39 +05:30
|
|
|
if (key && await key.checkBetterThanKeyInStorage(this.keyLoader, txn)) {
|
|
|
|
return key;
|
2021-03-01 19:34:45 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
// look only in the cache after looking into newKeys as it may contains that are better
|
2021-10-22 21:16:39 +05:30
|
|
|
const cachedKey = this.keyLoader.getCachedKey(roomId, senderKey, sessionId);
|
|
|
|
if (cachedKey) {
|
|
|
|
return cachedKey;
|
2021-03-01 19:34:45 +05:30
|
|
|
}
|
2021-10-22 21:16:39 +05:30
|
|
|
const storageKey = await keyFromStorage(roomId, senderKey, sessionId, txn);
|
|
|
|
if (storageKey && storageKey.serializationKey) {
|
|
|
|
return storageKey;
|
2020-09-04 15:36:26 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-08 14:22:02 +05:30
|
|
|
/**
|
2021-03-01 19:34:45 +05:30
|
|
|
* Writes the key as an inbound group session if there is not already a better key in the store
|
|
|
|
*/
|
2021-10-22 21:16:39 +05:30
|
|
|
writeRoomKey(key: IIncomingRoomKey, txn: Transaction): Promise<boolean> {
|
|
|
|
return key.write(this.keyLoader, txn);
|
2021-03-01 19:34:45 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extracts room keys from decrypted device messages.
|
|
|
|
* The key won't be persisted yet, you need to call RoomKey.write for that.
|
2020-09-08 14:22:02 +05:30
|
|
|
*/
|
2021-10-22 21:16:39 +05:30
|
|
|
roomKeysFromDeviceMessages(decryptionResults: DecryptionResult[], log: LogItem): IIncomingRoomKey[] {
|
|
|
|
let keys: IIncomingRoomKey[] = [];
|
2021-03-01 19:34:45 +05:30
|
|
|
for (const dr of decryptionResults) {
|
|
|
|
if (dr.event?.type !== "m.room_key" || dr.event.content?.algorithm !== MEGOLM_ALGORITHM) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
log.wrap("room_key", log => {
|
2021-10-22 21:16:39 +05:30
|
|
|
const key = keyFromDeviceMessage(dr);
|
2021-03-01 19:34:45 +05:30
|
|
|
if (key) {
|
|
|
|
log.set("roomId", key.roomId);
|
|
|
|
log.set("id", key.sessionId);
|
|
|
|
keys.push(key);
|
|
|
|
} else {
|
2021-02-17 23:15:04 +05:30
|
|
|
log.logLevel = log.level.Warn;
|
|
|
|
log.set("invalid", true);
|
2020-09-17 21:29:02 +05:30
|
|
|
}
|
2021-02-17 23:15:04 +05:30
|
|
|
}, log.level.Detail);
|
2020-09-17 17:50:15 +05:30
|
|
|
}
|
2021-03-01 19:34:45 +05:30
|
|
|
return keys;
|
2020-09-17 21:29:02 +05:30
|
|
|
}
|
2020-09-14 17:49:35 +05:30
|
|
|
|
2021-10-22 21:16:39 +05:30
|
|
|
roomKeyFromBackup(roomId: string, sessionId: string, sessionInfo: string): IIncomingRoomKey | undefined {
|
|
|
|
return keyFromBackup(roomId, sessionId, sessionInfo);
|
2020-09-02 17:54:38 +05:30
|
|
|
}
|
2020-09-04 15:36:26 +05:30
|
|
|
|
2021-10-22 21:16:39 +05:30
|
|
|
dispose() {
|
|
|
|
this.keyLoader.dispose();
|
|
|
|
}
|
|
|
|
}
|