implement storing room keys
This commit is contained in:
parent
f5c7b1b3ec
commit
6d3aa219fa
6 changed files with 132 additions and 8 deletions
|
@ -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) {
|
||||
|
|
68
src/matrix/e2ee/megolm/Decryption.js
Normal file
68
src/matrix/e2ee/megolm/Decryption.js
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"});
|
||||
}
|
||||
|
|
36
src/matrix/storage/idb/stores/InboundGroupSessionStore.js
Normal file
36
src/matrix/storage/idb/stores/InboundGroupSessionStore.js
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
Reference in a new issue