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";
|
const PENDING_ENCRYPTED_EVENTS = "pendingEncryptedDeviceEvents";
|
||||||
|
|
||||||
export class DeviceMessageHandler {
|
export class DeviceMessageHandler {
|
||||||
constructor({storage, olmDecryption, megolmEncryption}) {
|
constructor({storage}) {
|
||||||
this._storage = storage;
|
this._storage = storage;
|
||||||
|
this._olmDecryption = null;
|
||||||
|
this._megolmDecryption = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
enableEncryption({olmDecryption, megolmDecryption}) {
|
||||||
this._olmDecryption = olmDecryption;
|
this._olmDecryption = olmDecryption;
|
||||||
this._megolmEncryption = megolmEncryption;
|
this._megolmDecryption = megolmDecryption;
|
||||||
}
|
}
|
||||||
|
|
||||||
async writeSync(toDeviceEvents, txn) {
|
async writeSync(toDeviceEvents, txn) {
|
||||||
|
@ -35,25 +40,28 @@ export class DeviceMessageHandler {
|
||||||
// we don't handle anything other for now
|
// we don't handle anything other for now
|
||||||
}
|
}
|
||||||
|
|
||||||
async _handleDecryptedEvents(payloads, txn) {
|
async _writeDecryptedEvents(payloads, txn) {
|
||||||
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 megolmChanges;
|
||||||
if (megOlmRoomKeysPayloads.length) {
|
if (megOlmRoomKeysPayloads.length) {
|
||||||
megolmChanges = await this._megolmEncryption.addRoomKeys(megOlmRoomKeysPayloads, txn);
|
megolmChanges = await this._megolmDecryption.addRoomKeys(megOlmRoomKeysPayloads, txn);
|
||||||
}
|
}
|
||||||
return {megolmChanges};
|
return {megolmChanges};
|
||||||
}
|
}
|
||||||
|
|
||||||
applyChanges({megolmChanges}) {
|
applyChanges({megolmChanges}) {
|
||||||
if (megolmChanges) {
|
if (megolmChanges) {
|
||||||
this._megolmEncryption.applyRoomKeyChanges(megolmChanges);
|
this._megolmDecryption.applyRoomKeyChanges(megolmChanges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// not safe to call multiple times without awaiting first call
|
// not safe to call multiple times without awaiting first call
|
||||||
async decryptPending() {
|
async decryptPending() {
|
||||||
|
if (!this._olmDecryption) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const readTxn = await this._storage.readTxn([this._storage.storeNames.session]);
|
const readTxn = await this._storage.readTxn([this._storage.storeNames.session]);
|
||||||
const pendingEvents = this._getPendingEvents(readTxn);
|
const pendingEvents = this._getPendingEvents(readTxn);
|
||||||
// only know olm for now
|
// only know olm for now
|
||||||
|
@ -66,11 +74,11 @@ export class DeviceMessageHandler {
|
||||||
// both to remove the pending events and to modify the olm account
|
// both to remove the pending events and to modify the olm account
|
||||||
this._storage.storeNames.session,
|
this._storage.storeNames.session,
|
||||||
this._storage.storeNames.olmSessions,
|
this._storage.storeNames.olmSessions,
|
||||||
// this._storage.storeNames.megolmInboundSessions,
|
this._storage.storeNames.inboundGroupSessions,
|
||||||
]);
|
]);
|
||||||
let changes;
|
let changes;
|
||||||
try {
|
try {
|
||||||
changes = await this._handleDecryptedEvent(decryptChanges.payloads, txn);
|
changes = await this._writeDecryptedEvents(decryptChanges.payloads, txn);
|
||||||
decryptChanges.write(txn);
|
decryptChanges.write(txn);
|
||||||
txn.session.remove(PENDING_ENCRYPTED_EVENTS);
|
txn.session.remove(PENDING_ENCRYPTED_EVENTS);
|
||||||
} catch (err) {
|
} 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",
|
"userIdentities",
|
||||||
"deviceIdentities",
|
"deviceIdentities",
|
||||||
"olmSessions",
|
"olmSessions",
|
||||||
|
"inboundGroupSessions",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const STORE_MAP = Object.freeze(STORE_NAMES.reduce((nameMap, name) => {
|
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 {UserIdentityStore} from "./stores/UserIdentityStore.js";
|
||||||
import {DeviceIdentityStore} from "./stores/DeviceIdentityStore.js";
|
import {DeviceIdentityStore} from "./stores/DeviceIdentityStore.js";
|
||||||
import {OlmSessionStore} from "./stores/OlmSessionStore.js";
|
import {OlmSessionStore} from "./stores/OlmSessionStore.js";
|
||||||
|
import {InboundGroupSessionStore} from "./stores/InboundGroupSessionStore.js";
|
||||||
|
|
||||||
export class Transaction {
|
export class Transaction {
|
||||||
constructor(txn, allowedStoreNames) {
|
constructor(txn, allowedStoreNames) {
|
||||||
|
@ -95,6 +96,10 @@ export class Transaction {
|
||||||
get olmSessions() {
|
get olmSessions() {
|
||||||
return this._store("olmSessions", idbStore => new OlmSessionStore(idbStore));
|
return this._store("olmSessions", idbStore => new OlmSessionStore(idbStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get inboundGroupSessions() {
|
||||||
|
return this._store("inboundGroupSessions", idbStore => new InboundGroupSessionStore(idbStore));
|
||||||
|
}
|
||||||
|
|
||||||
complete() {
|
complete() {
|
||||||
return txnAsPromise(this._txn);
|
return txnAsPromise(this._txn);
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const schema = [
|
||||||
migrateSession,
|
migrateSession,
|
||||||
createIdentityStores,
|
createIdentityStores,
|
||||||
createOlmSessionStore,
|
createOlmSessionStore,
|
||||||
|
createInboundGroupSessionsStore,
|
||||||
];
|
];
|
||||||
// TODO: how to deal with git merge conflicts of this array?
|
// TODO: how to deal with git merge conflicts of this array?
|
||||||
|
|
||||||
|
@ -76,3 +77,8 @@ function createIdentityStores(db) {
|
||||||
function createOlmSessionStore(db) {
|
function createOlmSessionStore(db) {
|
||||||
db.createObjectStore("olmSessions", {keyPath: "key"});
|
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