first draft of decryption in Room and RoomEncryption

This commit is contained in:
Bruno Windels 2020-09-04 12:09:19 +02:00
parent fab58e8724
commit 502ba5deea
5 changed files with 77 additions and 11 deletions

View file

@ -15,6 +15,7 @@ limitations under the License.
*/ */
import {OLM_ALGORITHM, MEGOLM_ALGORITHM} from "./e2ee/common.js"; import {OLM_ALGORITHM, MEGOLM_ALGORITHM} from "./e2ee/common.js";
import {groupBy} from "../utils/groupBy.js";
// key to store in session store // key to store in session store
const PENDING_ENCRYPTED_EVENTS = "pendingEncryptedDeviceEvents"; const PENDING_ENCRYPTED_EVENTS = "pendingEncryptedDeviceEvents";
@ -44,21 +45,21 @@ export class DeviceMessageHandler {
const megOlmRoomKeysPayloads = payloads.filter(p => { const megOlmRoomKeysPayloads = payloads.filter(p => {
return p.event?.type === "m.room_key" && p.event.content?.algorithm === MEGOLM_ALGORITHM; return p.event?.type === "m.room_key" && p.event.content?.algorithm === MEGOLM_ALGORITHM;
}); });
let megolmChanges; let roomKeys;
if (megOlmRoomKeysPayloads.length) { if (megOlmRoomKeysPayloads.length) {
megolmChanges = await this._megolmDecryption.addRoomKeys(megOlmRoomKeysPayloads, txn); roomKeys = await this._megolmDecryption.addRoomKeys(megOlmRoomKeysPayloads, txn);
} }
return {megolmChanges}; return {roomKeys};
} }
_applyDecryptChanges({megolmChanges}) { _applyDecryptChanges(rooms, {roomKeys}) {
if (megolmChanges) { const roomKeysByRoom = groupBy(roomKeys, s => s.roomId);
this._megolmDecryption.applyRoomKeyChanges(megolmChanges); for (const [roomId, roomKeys] of roomKeysByRoom) {
} }
} }
// not safe to call multiple times without awaiting first call // not safe to call multiple times without awaiting first call
async decryptPending() { async decryptPending(rooms) {
if (!this._olmDecryption) { if (!this._olmDecryption) {
return; return;
} }
@ -89,7 +90,7 @@ export class DeviceMessageHandler {
throw err; throw err;
} }
await txn.complete(); await txn.complete();
this._applyDecryptChanges(changes); this._applyDecryptChanges(rooms, changes);
} }
async _getPendingEvents(txn) { async _getPendingEvents(txn) {

View file

@ -150,7 +150,7 @@ export class Session {
} }
await this._e2eeAccount.generateOTKsIfNeeded(this._storage); await this._e2eeAccount.generateOTKsIfNeeded(this._storage);
await this._e2eeAccount.uploadKeys(this._storage); await this._e2eeAccount.uploadKeys(this._storage);
await this._deviceMessageHandler.decryptPending(); await this._deviceMessageHandler.decryptPending(this.rooms);
} }
} }
@ -285,7 +285,7 @@ export class Session {
async afterSyncCompleted() { async afterSyncCompleted() {
const needsToUploadOTKs = await this._e2eeAccount.generateOTKsIfNeeded(this._storage); const needsToUploadOTKs = await this._e2eeAccount.generateOTKsIfNeeded(this._storage);
const promises = [this._deviceMessageHandler.decryptPending()]; const promises = [this._deviceMessageHandler.decryptPending(this.rooms)];
if (needsToUploadOTKs) { if (needsToUploadOTKs) {
// TODO: we could do this in parallel with sync if it proves to be too slow // TODO: we could do this in parallel with sync if it proves to be too slow
// but I'm not sure how to not swallow errors in that case // but I'm not sure how to not swallow errors in that case

View file

@ -27,12 +27,39 @@ export class RoomEncryption {
this._megolmEncryption = megolmEncryption; this._megolmEncryption = megolmEncryption;
// content of the m.room.encryption event // content of the m.room.encryption event
this._encryptionParams = encryptionParams; this._encryptionParams = encryptionParams;
this._megolmBackfillCache = this._megolmDecryption.createSessionCache();
this._megolmSyncCache = this._megolmDecryption.createSessionCache();
}
notifyTimelineClosed() {
// empty the backfill cache when closing the timeline
this._megolmBackfillCache.dispose();
this._megolmBackfillCache = this._megolmDecryption.createSessionCache();
} }
async writeMemberChanges(memberChanges, txn) { async writeMemberChanges(memberChanges, txn) {
return await this._deviceTracker.writeMemberChanges(this._room, memberChanges, txn); return await this._deviceTracker.writeMemberChanges(this._room, memberChanges, txn);
} }
async decryptNewSyncEvent(id, event, txn) {
const payload = await this._megolmDecryption.decryptNewEvent(
this._room.id, event, this._megolmSyncCache, txn);
return payload;
}
async decryptNewGapEvent(id, event, txn) {
const payload = await this._megolmDecryption.decryptNewEvent(
this._room.id, event, this._megolmBackfillCache, txn);
return payload;
}
async decryptStoredEvent(id, event, txn) {
const payload = await this._megolmDecryption.decryptStoredEvent(
this._room.id, event, this._megolmBackfillCache, txn);
return payload;
}
async encrypt(type, content, hsApi) { async encrypt(type, content, hsApi) {
const megolmResult = await this._megolmEncryption.encrypt(this._room.id, type, content, this._encryptionParams); const megolmResult = await this._megolmEncryption.encrypt(this._room.id, type, content, this._encryptionParams);
// share the new megolm session if needed // share the new megolm session if needed

View file

@ -45,6 +45,22 @@ export class Room extends EventEmitter {
this._roomEncryption = null; this._roomEncryption = null;
} }
async _decryptSyncEntries(entries, txn) {
await Promise.all(entries.map(async e => {
if (e.eventType === "m.room.encrypted") {
try {
const decryptedEvent = await this._roomEncryption.decryptNewSyncEvent(e.internalId, e.event, txn);
if (decryptedEvent) {
e.replaceWithDecrypted(decryptedEvent);
}
} catch (err) {
e.setDecryptionError(err);
}
}
}));
return entries;
}
/** @package */ /** @package */
async writeSync(roomResponse, membership, isInitialSync, txn) { async writeSync(roomResponse, membership, isInitialSync, txn) {
const isTimelineOpen = !!this._timeline; const isTimelineOpen = !!this._timeline;
@ -53,7 +69,13 @@ export class Room extends EventEmitter {
membership, membership,
isInitialSync, isTimelineOpen, isInitialSync, isTimelineOpen,
txn); txn);
const {entries, newLiveKey, memberChanges} = await this._syncWriter.writeSync(roomResponse, txn); const {entries: encryptedEntries, newLiveKey, memberChanges} =
await this._syncWriter.writeSync(roomResponse, txn);
// decrypt if applicable
let entries = encryptedEntries;
if (this._roomEncryption) {
entries = await this._decryptSyncEntries(encryptedEntries, txn);
}
// fetch new members while we have txn open, // fetch new members while we have txn open,
// but don't make any in-memory changes yet // but don't make any in-memory changes yet
let heroChanges; let heroChanges;
@ -341,6 +363,9 @@ export class Room extends EventEmitter {
closeCallback: () => { closeCallback: () => {
console.log(`closing the timeline for ${this._roomId}`); console.log(`closing the timeline for ${this._roomId}`);
this._timeline = null; this._timeline = null;
if (this._roomEncryption) {
this._roomEncryption.notifyTimelineClosed();
}
}, },
user: this._user, user: this._user,
}); });

View file

@ -21,6 +21,7 @@ export class EventEntry extends BaseEntry {
constructor(eventEntry, fragmentIdComparer) { constructor(eventEntry, fragmentIdComparer) {
super(fragmentIdComparer); super(fragmentIdComparer);
this._eventEntry = eventEntry; this._eventEntry = eventEntry;
this._decryptionError = null;
} }
get fragmentId() { get fragmentId() {
@ -31,6 +32,10 @@ export class EventEntry extends BaseEntry {
return this._eventEntry.eventIndex; return this._eventEntry.eventIndex;
} }
get internalId() {
return `${this.fragmentId}|${this.entryIndex}`;
}
get content() { get content() {
return this._eventEntry.event.content; return this._eventEntry.event.content;
} }
@ -66,4 +71,12 @@ export class EventEntry extends BaseEntry {
get id() { get id() {
return this._eventEntry.event.event_id; return this._eventEntry.event.event_id;
} }
replaceWithDecrypted(event) {
this._eventEntry.event = event;
}
setDecryptionError(err) {
this._decryptionError = err;
}
} }