diff --git a/src/matrix/DeviceMessageHandler.js b/src/matrix/DeviceMessageHandler.js index a26bfe33..7154d192 100644 --- a/src/matrix/DeviceMessageHandler.js +++ b/src/matrix/DeviceMessageHandler.js @@ -20,10 +20,15 @@ import {OLM_ALGORITHM, MEGOLM_ALGORITHM} from "./e2ee/common.js"; const PENDING_ENCRYPTED_EVENTS = "pendingEncryptedDeviceEvents"; export class DeviceMessageHandler { - constructor({storage, olmDecryption, megolmEncryption}) { + constructor({storage}) { this._storage = storage; + this._olmDecryption = null; + this._megolmDecryption = null; + } + + enableEncryption({olmDecryption, megolmDecryption}) { this._olmDecryption = olmDecryption; - this._megolmEncryption = megolmEncryption; + this._megolmDecryption = megolmDecryption; } async writeSync(toDeviceEvents, txn) { @@ -35,25 +40,28 @@ export class DeviceMessageHandler { // we don't handle anything other for now } - async _handleDecryptedEvents(payloads, txn) { + async _writeDecryptedEvents(payloads, txn) { 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; if (megOlmRoomKeysPayloads.length) { - megolmChanges = await this._megolmEncryption.addRoomKeys(megOlmRoomKeysPayloads, txn); + megolmChanges = await this._megolmDecryption.addRoomKeys(megOlmRoomKeysPayloads, txn); } return {megolmChanges}; } applyChanges({megolmChanges}) { if (megolmChanges) { - this._megolmEncryption.applyRoomKeyChanges(megolmChanges); + this._megolmDecryption.applyRoomKeyChanges(megolmChanges); } } // not safe to call multiple times without awaiting first call async decryptPending() { + if (!this._olmDecryption) { + return; + } const readTxn = await this._storage.readTxn([this._storage.storeNames.session]); const pendingEvents = this._getPendingEvents(readTxn); // only know olm for now @@ -66,11 +74,11 @@ export class DeviceMessageHandler { // both to remove the pending events and to modify the olm account this._storage.storeNames.session, this._storage.storeNames.olmSessions, - // this._storage.storeNames.megolmInboundSessions, + this._storage.storeNames.inboundGroupSessions, ]); let changes; try { - changes = await this._handleDecryptedEvent(decryptChanges.payloads, txn); + changes = await this._writeDecryptedEvents(decryptChanges.payloads, txn); decryptChanges.write(txn); txn.session.remove(PENDING_ENCRYPTED_EVENTS); } catch (err) { diff --git a/src/matrix/e2ee/megolm/Decryption.js b/src/matrix/e2ee/megolm/Decryption.js new file mode 100644 index 00000000..1627564d --- /dev/null +++ b/src/matrix/e2ee/megolm/Decryption.js @@ -0,0 +1,68 @@ +/* +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. +*/ + +// senderKey is a curve25519 key +export class Decryption { + constructor({pickleKey}) { + this._pickleKey = pickleKey; + } + + async addRoomKeys(payloads, txn) { + const newSessions = []; + for (const {senderKey, event} of payloads) { + const roomId = event.content?.["room_id"]; + const sessionId = event.content?.["session_id"]; + const sessionKey = event.content?.["session_key"]; + + if ( + typeof roomId !== "string" || + typeof sessionId !== "string" || + typeof senderKey !== "string" || + typeof sessionKey !== "string" + ) { + return; + } + + const hasSession = await txn.inboundGroupSessions.has(roomId, senderKey, sessionId); + if (!hasSession) { + const session = new this._olm.InboundGroupSession(); + try { + session.create(sessionKey); + const sessionEntry = { + roomId, + senderKey, + sessionId, + session: session.pickle(this._pickleKey), + claimedKeys: event.keys, + }; + txn.megOlmInboundSessions.set(sessionEntry); + newSessions.push(sessionEntry); + } finally { + session.free(); + } + } + + } + return newSessions; + } + + applyRoomKeyChanges(newSessions) { + // retry decryption with the new sessions + if (newSessions.length) { + console.log(`I have ${newSessions.length} new inbound group sessions`, newSessions) + } + } +} diff --git a/src/matrix/storage/common.js b/src/matrix/storage/common.js index 73900af3..76a60e66 100644 --- a/src/matrix/storage/common.js +++ b/src/matrix/storage/common.js @@ -25,6 +25,7 @@ export const STORE_NAMES = Object.freeze([ "userIdentities", "deviceIdentities", "olmSessions", + "inboundGroupSessions", ]); export const STORE_MAP = Object.freeze(STORE_NAMES.reduce((nameMap, name) => { diff --git a/src/matrix/storage/idb/Transaction.js b/src/matrix/storage/idb/Transaction.js index 370a5563..fa862c08 100644 --- a/src/matrix/storage/idb/Transaction.js +++ b/src/matrix/storage/idb/Transaction.js @@ -27,6 +27,7 @@ import {PendingEventStore} from "./stores/PendingEventStore.js"; import {UserIdentityStore} from "./stores/UserIdentityStore.js"; import {DeviceIdentityStore} from "./stores/DeviceIdentityStore.js"; import {OlmSessionStore} from "./stores/OlmSessionStore.js"; +import {InboundGroupSessionStore} from "./stores/InboundGroupSessionStore.js"; export class Transaction { constructor(txn, allowedStoreNames) { @@ -95,6 +96,10 @@ export class Transaction { get olmSessions() { return this._store("olmSessions", idbStore => new OlmSessionStore(idbStore)); } + + get inboundGroupSessions() { + return this._store("inboundGroupSessions", idbStore => new InboundGroupSessionStore(idbStore)); + } complete() { return txnAsPromise(this._txn); diff --git a/src/matrix/storage/idb/schema.js b/src/matrix/storage/idb/schema.js index 8e34ac27..81a56991 100644 --- a/src/matrix/storage/idb/schema.js +++ b/src/matrix/storage/idb/schema.js @@ -11,6 +11,7 @@ export const schema = [ migrateSession, createIdentityStores, createOlmSessionStore, + createInboundGroupSessionsStore, ]; // TODO: how to deal with git merge conflicts of this array? @@ -76,3 +77,8 @@ function createIdentityStores(db) { function createOlmSessionStore(db) { db.createObjectStore("olmSessions", {keyPath: "key"}); } + +//v6 +function createInboundGroupSessionsStore(db) { + db.createObjectStore("inboundGroupSessions", {keyPath: "key"}); +} diff --git a/src/matrix/storage/idb/stores/InboundGroupSessionStore.js b/src/matrix/storage/idb/stores/InboundGroupSessionStore.js new file mode 100644 index 00000000..3de5a103 --- /dev/null +++ b/src/matrix/storage/idb/stores/InboundGroupSessionStore.js @@ -0,0 +1,36 @@ +/* +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. +*/ + +function encodeKey(roomId, senderKey, sessionId) { + return `${roomId}|${senderKey}|${sessionId}`; +} + +export class InboundGroupSessionStore { + constructor(store) { + this._store = store; + } + + async has(roomId, senderKey, sessionId) { + const key = encodeKey(roomId, senderKey, sessionId); + const fetchedKey = await this._store.getKey(key); + return key === fetchedKey; + } + + set(session) { + session.key = encodeKey(session.roomId, session.senderKey, session.sessionId); + this._store.put(session); + } +}