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.
|
|
|
|
*/
|
|
|
|
|
2021-03-01 15:04:45 +01:00
|
|
|
import {OLM_ALGORITHM} from "./e2ee/common.js";
|
2021-10-21 14:40:51 +02:00
|
|
|
import {countBy, groupBy} from "../utils/groupBy";
|
2022-03-17 11:31:12 +01:00
|
|
|
import {LRUCache} from "../utils/LRUCache";
|
2023-02-27 19:01:30 +01:00
|
|
|
import {EventEmitter} from "../utils/EventEmitter";
|
2020-09-02 13:33:27 +02:00
|
|
|
|
2023-02-27 19:01:30 +01:00
|
|
|
export class DeviceMessageHandler extends EventEmitter{
|
2022-02-14 17:14:21 +01:00
|
|
|
constructor({storage, callHandler}) {
|
2023-02-27 19:01:30 +01:00
|
|
|
super();
|
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;
|
2022-02-14 17:14:21 +01:00
|
|
|
this._callHandler = callHandler;
|
|
|
|
this._senderDeviceCache = new LRUCache(10, di => di.curve25519Key);
|
2020-09-02 14:24:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-03-01 15:04:45 +01:00
|
|
|
obtainSyncLock(toDeviceEvents) {
|
|
|
|
return this._olmDecryption?.obtainDecryptionLock(toDeviceEvents);
|
2020-09-02 13:33:27 +02:00
|
|
|
}
|
|
|
|
|
2021-03-01 15:04:45 +01:00
|
|
|
async prepareSync(toDeviceEvents, lock, txn, log) {
|
|
|
|
log.set("messageTypes", countBy(toDeviceEvents, e => e.type));
|
|
|
|
const encryptedEvents = toDeviceEvents.filter(e => e.type === "m.room.encrypted");
|
2023-02-27 19:01:30 +01:00
|
|
|
this._emitUnencryptedEvents(toDeviceEvents);
|
2020-09-02 14:24:38 +02:00
|
|
|
if (!this._olmDecryption) {
|
2021-03-01 15:04:45 +01:00
|
|
|
log.log("can't decrypt, encryption not enabled", log.level.Warn);
|
2020-09-02 14:24:38 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-09-02 13:33:27 +02:00
|
|
|
// only know olm for now
|
2021-03-01 15:04:45 +01:00
|
|
|
const olmEvents = encryptedEvents.filter(e => e.content?.algorithm === OLM_ALGORITHM);
|
|
|
|
if (olmEvents.length) {
|
|
|
|
const olmDecryptChanges = await this._olmDecryption.decryptAll(olmEvents, lock, txn);
|
|
|
|
log.set("decryptedTypes", countBy(olmDecryptChanges.results, r => r.event?.type));
|
|
|
|
for (const err of olmDecryptChanges.errors) {
|
|
|
|
log.child("decrypt_error").catch(err);
|
|
|
|
}
|
|
|
|
const newRoomKeys = this._megolmDecryption.roomKeysFromDeviceMessages(olmDecryptChanges.results, log);
|
2022-04-07 16:50:16 +02:00
|
|
|
|
2022-02-14 17:14:21 +01:00
|
|
|
// TODO: somehow include rooms that received a call to_device message in the sync state?
|
|
|
|
// or have updates flow through event emitter?
|
|
|
|
// well, we don't really need to update the room other then when a call starts or stops
|
|
|
|
// any changes within the call will be emitted on the call object?
|
2021-03-01 15:04:45 +01:00
|
|
|
return new SyncPreparation(olmDecryptChanges, newRoomKeys);
|
2020-09-02 13:33:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-01 15:04:45 +01:00
|
|
|
/** check that prep is not undefined before calling this */
|
|
|
|
async writeSync(prep, txn) {
|
|
|
|
// write olm changes
|
|
|
|
prep.olmDecryptChanges.write(txn);
|
2022-01-27 16:07:18 +01:00
|
|
|
const didWriteValues = await Promise.all(prep.newRoomKeys.map(key => this._megolmDecryption.writeRoomKey(key, txn)));
|
2022-06-01 15:29:24 +02:00
|
|
|
const hasNewRoomKeys = didWriteValues.some(didWrite => !!didWrite);
|
|
|
|
return {
|
|
|
|
hasNewRoomKeys,
|
|
|
|
decryptionResults: prep.olmDecryptChanges.results
|
|
|
|
};
|
2021-03-01 15:04:45 +01:00
|
|
|
}
|
2022-02-14 17:14:21 +01:00
|
|
|
|
2022-06-01 15:29:24 +02:00
|
|
|
async afterSyncCompleted(decryptionResults, deviceTracker, hsApi, log) {
|
2023-02-27 19:01:30 +01:00
|
|
|
this._emitEncryptedEvents(decryptionResults);
|
2023-02-09 11:57:45 +01:00
|
|
|
if (this._callHandler) {
|
|
|
|
// if we don't have a device, we need to fetch the device keys the message claims
|
|
|
|
// and check the keys, and we should only do network requests during
|
|
|
|
// sync processing in the afterSyncCompleted step.
|
|
|
|
const callMessages = decryptionResults.filter(dr => this._callHandler.handlesDeviceMessageEventType(dr.event?.type));
|
|
|
|
if (callMessages.length) {
|
|
|
|
await log.wrap("process call signalling messages", async log => {
|
|
|
|
for (const dr of callMessages) {
|
|
|
|
// serialize device loading, so subsequent messages for the same device take advantage of the cache
|
|
|
|
const device = await deviceTracker.deviceForId(dr.event.sender, dr.event.content.device_id, hsApi, log);
|
|
|
|
dr.setDevice(device);
|
|
|
|
if (dr.isVerified) {
|
|
|
|
this._callHandler.handleDeviceMessage(dr.event, dr.userId, dr.deviceId, log);
|
|
|
|
} else {
|
|
|
|
log.log({
|
|
|
|
l: "could not verify olm fingerprint key matches, ignoring",
|
|
|
|
ed25519Key: dr.device.ed25519Key,
|
|
|
|
claimedEd25519Key: dr.claimedEd25519Key,
|
|
|
|
deviceId: device.deviceId,
|
|
|
|
userId: device.userId,
|
|
|
|
});
|
|
|
|
}
|
2022-06-01 15:29:24 +02:00
|
|
|
}
|
2023-02-09 11:57:45 +01:00
|
|
|
});
|
|
|
|
}
|
2022-02-14 17:14:21 +01:00
|
|
|
}
|
|
|
|
}
|
2023-02-27 19:01:30 +01:00
|
|
|
|
|
|
|
_emitUnencryptedEvents(toDeviceEvents) {
|
|
|
|
const unencryptedEvents = toDeviceEvents.filter(e => e.type !== "m.room.encrypted");
|
|
|
|
for (const event of unencryptedEvents) {
|
|
|
|
this.emit("message", { unencrypted: event });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_emitEncryptedEvents(decryptionResults) {
|
|
|
|
for (const result of decryptionResults) {
|
|
|
|
this.emit("message", { encrypted: result });
|
|
|
|
}
|
|
|
|
}
|
2021-03-01 15:04:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class SyncPreparation {
|
|
|
|
constructor(olmDecryptChanges, newRoomKeys) {
|
|
|
|
this.olmDecryptChanges = olmDecryptChanges;
|
|
|
|
this.newRoomKeys = newRoomKeys;
|
|
|
|
this.newKeysByRoom = groupBy(newRoomKeys, r => r.roomId);
|
|
|
|
}
|
2020-09-02 13:33:27 +02:00
|
|
|
}
|