fetch and verify keys on olm call signalling message

This commit is contained in:
Bruno Windels 2022-06-01 15:29:24 +02:00
parent 50ae51e893
commit 9efe294a79
4 changed files with 41 additions and 37 deletions

View File

@ -38,7 +38,6 @@ export class DeviceMessageHandler {
async prepareSync(toDeviceEvents, lock, txn, log) {
log.set("messageTypes", countBy(toDeviceEvents, e => e.type));
this._handleUnencryptedCallEvents(toDeviceEvents, log);
const encryptedEvents = toDeviceEvents.filter(e => e.type === "m.room.encrypted");
if (!this._olmDecryption) {
log.log("can't decrypt, encryption not enabled", log.level.Warn);
@ -54,20 +53,6 @@ export class DeviceMessageHandler {
}
const newRoomKeys = this._megolmDecryption.roomKeysFromDeviceMessages(olmDecryptChanges.results, log);
// const callMessages = olmDecryptChanges.results.filter(dr => this._callHandler.handlesDeviceMessageEventType(dr.event?.type));
// // load devices by sender key
// await Promise.all(callMessages.map(async dr => {
// dr.setDevice(await this._getDevice(dr.senderCurve25519Key, txn));
// }));
// // TODO: pass this in the prep and run it in afterSync or afterSyncComplete (as callHandler can send events as well)?
// for (const dr of callMessages) {
// if (dr.device) {
// this._callHandler.handleDeviceMessage(dr.event, dr.device.userId, dr.device.deviceId, log);
// } else {
// console.error("could not deliver message because don't have device for sender key", dr.event);
// }
// }
// 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
@ -76,33 +61,42 @@ export class DeviceMessageHandler {
}
}
_handleUnencryptedCallEvents(toDeviceEvents, log) {
const callMessages = toDeviceEvents.filter(e => this._callHandler.handlesDeviceMessageEventType(e.type));
for (const event of callMessages) {
const userId = event.sender;
const deviceId = event.content.device_id;
this._callHandler.handleDeviceMessage(event, userId, deviceId, log);
}
}
/** check that prep is not undefined before calling this */
async writeSync(prep, txn) {
// write olm changes
prep.olmDecryptChanges.write(txn);
const didWriteValues = await Promise.all(prep.newRoomKeys.map(key => this._megolmDecryption.writeRoomKey(key, txn)));
return didWriteValues.some(didWrite => !!didWrite);
const hasNewRoomKeys = didWriteValues.some(didWrite => !!didWrite);
return {
hasNewRoomKeys,
decryptionResults: prep.olmDecryptChanges.results
};
}
async _getDevice(senderKey, txn) {
let device = this._senderDeviceCache.get(senderKey);
if (!device) {
device = await txn.deviceIdentities.getByCurve25519Key(senderKey);
if (device) {
this._senderDeviceCache.set(device);
}
async afterSyncCompleted(decryptionResults, deviceTracker, hsApi, log) {
// 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, userId,
});
}
}
});
}
return device;
}
}

View File

@ -685,7 +685,9 @@ export class Session {
async writeSync(syncResponse, syncFilterId, preparation, txn, log) {
const changes = {
syncInfo: null,
e2eeAccountChanges: null
e2eeAccountChanges: null,
hasNewRoomKeys: false,
deviceMessageDecryptionResults: null,
};
const syncToken = syncResponse.next_batch;
if (syncToken !== this.syncToken) {
@ -706,7 +708,9 @@ export class Session {
}
if (preparation) {
changes.hasNewRoomKeys = await log.wrap("deviceMsgs", log => this._deviceMessageHandler.writeSync(preparation, txn, log));
const {hasNewRoomKeys, decryptionResults} = await log.wrap("deviceMsgs", log => this._deviceMessageHandler.writeSync(preparation, txn, log));
changes.hasNewRoomKeys = hasNewRoomKeys;
changes.deviceMessageDecryptionResults = decryptionResults;
}
// store account data
@ -747,6 +751,9 @@ export class Session {
if (changes.hasNewRoomKeys) {
this._keyBackup.get()?.flush(log);
}
if (changes.deviceMessageDecryptionResults) {
await this._deviceMessageHandler.afterSyncCompleted(changes.deviceMessageDecryptionResults, this._deviceTracker, this._hsApi, log);
}
}
_tryReplaceRoomBeingCreated(roomId, log) {

View File

@ -160,7 +160,7 @@ export class Sync {
const isCatchupSync = this._status.get() === SyncStatus.CatchupSync;
const sessionPromise = (async () => {
try {
await log.wrap("session", log => this._session.afterSyncCompleted(sessionChanges, isCatchupSync, log), log.level.Detail);
await log.wrap("session", log => this._session.afterSyncCompleted(sessionChanges, isCatchupSync, log));
} catch (err) {} // error is logged, but don't fail sessionPromise
})();

View File

@ -41,5 +41,8 @@ Runs before any room.prepareSync, so the new room keys can be passed to each roo
- e2ee account
- generate more otks if needed
- upload new otks if needed or device keys if not uploaded before
- device message handler:
- fetch keys we don't know about yet for (call) to_device messages identity
- pass signalling messages to call handler
- rooms
- share new room keys if needed