2020-09-02 13:33:27 +02:00
|
|
|
/*
|
|
|
|
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 {OLM_ALGORITHM, MEGOLM_ALGORITHM} from "./e2ee/common.js";
|
|
|
|
|
|
|
|
// key to store in session store
|
|
|
|
const PENDING_ENCRYPTED_EVENTS = "pendingEncryptedDeviceEvents";
|
|
|
|
|
|
|
|
export class DeviceMessageHandler {
|
2020-09-02 14:24:38 +02:00
|
|
|
constructor({storage}) {
|
2020-09-02 13:33:27 +02:00
|
|
|
this._storage = storage;
|
2020-09-02 14:24:38 +02:00
|
|
|
this._olmDecryption = null;
|
|
|
|
this._megolmDecryption = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
enableEncryption({olmDecryption, megolmDecryption}) {
|
2020-09-02 13:33:27 +02:00
|
|
|
this._olmDecryption = olmDecryption;
|
2020-09-02 14:24:38 +02:00
|
|
|
this._megolmDecryption = megolmDecryption;
|
2020-09-02 13:33:27 +02:00
|
|
|
}
|
|
|
|
|
2020-09-24 10:52:56 +02:00
|
|
|
/**
|
|
|
|
* @return {bool} whether messages are waiting to be decrypted and `decryptPending` should be called.
|
|
|
|
*/
|
2020-09-02 13:33:27 +02:00
|
|
|
async writeSync(toDeviceEvents, txn) {
|
|
|
|
const encryptedEvents = toDeviceEvents.filter(e => e.type === "m.room.encrypted");
|
2020-09-24 10:52:56 +02:00
|
|
|
if (!encryptedEvents.length) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-09-02 13:33:27 +02:00
|
|
|
// store encryptedEvents
|
2020-09-02 14:52:02 +02:00
|
|
|
let pendingEvents = await this._getPendingEvents(txn);
|
2020-09-02 13:33:27 +02:00
|
|
|
pendingEvents = pendingEvents.concat(encryptedEvents);
|
|
|
|
txn.session.set(PENDING_ENCRYPTED_EVENTS, pendingEvents);
|
|
|
|
// we don't handle anything other for now
|
2020-09-24 10:52:56 +02:00
|
|
|
return true;
|
2020-09-02 13:33:27 +02:00
|
|
|
}
|
|
|
|
|
2020-09-08 10:48:11 +02:00
|
|
|
/**
|
|
|
|
* [_writeDecryptedEvents description]
|
|
|
|
* @param {Array<DecryptionResult>} olmResults
|
|
|
|
* @param {[type]} txn [description]
|
|
|
|
* @return {[type]} [description]
|
|
|
|
*/
|
|
|
|
async _writeDecryptedEvents(olmResults, txn) {
|
|
|
|
const megOlmRoomKeysResults = olmResults.filter(r => {
|
|
|
|
return r.event?.type === "m.room_key" && r.event.content?.algorithm === MEGOLM_ALGORITHM;
|
2020-09-02 13:33:27 +02:00
|
|
|
});
|
2020-09-04 12:09:19 +02:00
|
|
|
let roomKeys;
|
2020-09-08 10:48:11 +02:00
|
|
|
if (megOlmRoomKeysResults.length) {
|
2020-09-08 10:55:38 +02:00
|
|
|
console.log("new room keys", megOlmRoomKeysResults);
|
2020-09-08 10:48:11 +02:00
|
|
|
roomKeys = await this._megolmDecryption.addRoomKeys(megOlmRoomKeysResults, txn);
|
2020-09-02 13:33:27 +02:00
|
|
|
}
|
2020-09-04 12:09:19 +02:00
|
|
|
return {roomKeys};
|
2020-09-02 13:33:27 +02:00
|
|
|
}
|
|
|
|
|
2020-09-23 17:34:25 +02:00
|
|
|
async _applyDecryptChanges(rooms, {roomKeys}) {
|
|
|
|
if (Array.isArray(roomKeys)) {
|
|
|
|
for (const roomKey of roomKeys) {
|
|
|
|
const room = rooms.get(roomKey.roomId);
|
|
|
|
// TODO: this is less parallized than it could be (like sync)
|
|
|
|
await room?.notifyRoomKey(roomKey);
|
2020-09-08 18:27:12 +02:00
|
|
|
}
|
2020-09-02 13:33:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// not safe to call multiple times without awaiting first call
|
2020-09-04 12:09:19 +02:00
|
|
|
async decryptPending(rooms) {
|
2020-09-02 14:24:38 +02:00
|
|
|
if (!this._olmDecryption) {
|
|
|
|
return;
|
|
|
|
}
|
2020-09-02 13:33:27 +02:00
|
|
|
const readTxn = await this._storage.readTxn([this._storage.storeNames.session]);
|
2020-09-02 14:52:02 +02:00
|
|
|
const pendingEvents = await this._getPendingEvents(readTxn);
|
2020-09-03 15:30:54 +02:00
|
|
|
if (pendingEvents.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
2020-09-02 13:33:27 +02:00
|
|
|
// only know olm for now
|
|
|
|
const olmEvents = pendingEvents.filter(e => e.content?.algorithm === OLM_ALGORITHM);
|
|
|
|
const decryptChanges = await this._olmDecryption.decryptAll(olmEvents);
|
|
|
|
for (const err of decryptChanges.errors) {
|
|
|
|
console.warn("decryption failed for event", err, err.event);
|
|
|
|
}
|
|
|
|
const txn = await this._storage.readWriteTxn([
|
|
|
|
// both to remove the pending events and to modify the olm account
|
|
|
|
this._storage.storeNames.session,
|
|
|
|
this._storage.storeNames.olmSessions,
|
2020-09-02 14:24:38 +02:00
|
|
|
this._storage.storeNames.inboundGroupSessions,
|
2020-09-02 13:33:27 +02:00
|
|
|
]);
|
|
|
|
let changes;
|
|
|
|
try {
|
2020-09-08 10:48:11 +02:00
|
|
|
changes = await this._writeDecryptedEvents(decryptChanges.results, txn);
|
2020-09-02 13:33:27 +02:00
|
|
|
decryptChanges.write(txn);
|
|
|
|
txn.session.remove(PENDING_ENCRYPTED_EVENTS);
|
|
|
|
} catch (err) {
|
|
|
|
txn.abort();
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
await txn.complete();
|
2020-09-23 17:34:25 +02:00
|
|
|
await this._applyDecryptChanges(rooms, changes);
|
2020-09-02 13:33:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async _getPendingEvents(txn) {
|
|
|
|
return (await txn.session.get(PENDING_ENCRYPTED_EVENTS)) || [];
|
|
|
|
}
|
|
|
|
}
|