forked from mystiq/hydrogen-web
first draft of decryption in Room and RoomEncryption
This commit is contained in:
parent
fab58e8724
commit
502ba5deea
5 changed files with 77 additions and 11 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue