integrate session backup with room encryption and megolm decryption
This commit is contained in:
parent
3941af93d2
commit
17fc249fa8
2 changed files with 110 additions and 30 deletions
|
@ -37,6 +37,13 @@ export class RoomEncryption {
|
||||||
this._eventIdsByMissingSession = new Map();
|
this._eventIdsByMissingSession = new Map();
|
||||||
this._senderDeviceCache = new Map();
|
this._senderDeviceCache = new Map();
|
||||||
this._storage = storage;
|
this._storage = storage;
|
||||||
|
this._sessionBackup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSessionBackup(sessionBackup) {
|
||||||
|
this._sessionBackup = sessionBackup;
|
||||||
|
// TODO: query session backup for all missing sessions so far
|
||||||
|
// can we query multiple? no, only for sessionId, all for room, or all
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyTimelineClosed() {
|
notifyTimelineClosed() {
|
||||||
|
@ -125,13 +132,53 @@ export class RoomEncryption {
|
||||||
const sessionId = event.content?.["session_id"];
|
const sessionId = event.content?.["session_id"];
|
||||||
const key = `${senderKey}|${sessionId}`;
|
const key = `${senderKey}|${sessionId}`;
|
||||||
let eventIds = this._eventIdsByMissingSession.get(key);
|
let eventIds = this._eventIdsByMissingSession.get(key);
|
||||||
|
// new missing session
|
||||||
if (!eventIds) {
|
if (!eventIds) {
|
||||||
|
this._requestMissingSessionFromBackup(sessionId).catch(err => {
|
||||||
|
console.error(`Could not get session ${sessionId} from backup`, err);
|
||||||
|
});
|
||||||
eventIds = new Set();
|
eventIds = new Set();
|
||||||
this._eventIdsByMissingSession.set(key, eventIds);
|
this._eventIdsByMissingSession.set(key, eventIds);
|
||||||
}
|
}
|
||||||
eventIds.add(event.event_id);
|
eventIds.add(event.event_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _requestMissingSessionFromBackup(sessionId) {
|
||||||
|
if (!this._sessionBackup) {
|
||||||
|
// somehow prompt for passphrase here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const session = await this._sessionBackup.getSession(this._room.id, sessionId);
|
||||||
|
if (session?.algorithm === MEGOLM_ALGORITHM) {
|
||||||
|
const txn = await this._storage.readWriteTxn([this._storage.storeNames.inboundGroupSessions]);
|
||||||
|
let roomKey;
|
||||||
|
try {
|
||||||
|
roomKey = await this._megolmDecryption.addRoomKeyFromBackup(
|
||||||
|
this._room.id, sessionId, session, txn);
|
||||||
|
} catch (err) {
|
||||||
|
txn.abort();
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
await txn.complete();
|
||||||
|
|
||||||
|
if (roomKey) {
|
||||||
|
// this will call into applyRoomKeys below
|
||||||
|
await this._room.notifyRoomKeys([roomKey]);
|
||||||
|
}
|
||||||
|
} else if (session?.algorithm) {
|
||||||
|
console.info(`Backed-up session of unknown algorithm: ${session.algorithm}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {RoomKeyDescription}
|
||||||
|
* @property {RoomKeyDescription} senderKey the curve25519 key of the sender
|
||||||
|
* @property {RoomKeyDescription} sessionId
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {Array<RoomKeyDescription>} roomKeys
|
||||||
|
* @return {Array<string>} the event ids that should be retried to decrypt
|
||||||
|
*/
|
||||||
applyRoomKeys(roomKeys) {
|
applyRoomKeys(roomKeys) {
|
||||||
// retry decryption with the new sessions
|
// retry decryption with the new sessions
|
||||||
const retryEventIds = [];
|
const retryEventIds = [];
|
||||||
|
|
|
@ -138,40 +138,73 @@ export class Decryption {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = new this._olm.InboundGroupSession();
|
const sessionEntry = await this._writeInboundSession(
|
||||||
try {
|
roomId, senderKey, claimedEd25519Key, sessionId, sessionKey, txn);
|
||||||
session.create(sessionKey);
|
if (sessionEntry) {
|
||||||
|
newSessions.push(sessionEntry);
|
||||||
let incomingSessionIsBetter = true;
|
|
||||||
const existingSessionEntry = await txn.inboundGroupSessions.get(roomId, senderKey, sessionId);
|
|
||||||
if (existingSessionEntry) {
|
|
||||||
const existingSession = new this._olm.InboundGroupSession();
|
|
||||||
try {
|
|
||||||
existingSession.unpickle(this._pickleKey, existingSessionEntry.session);
|
|
||||||
incomingSessionIsBetter = session.first_known_index() < existingSession.first_known_index();
|
|
||||||
} finally {
|
|
||||||
existingSession.free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (incomingSessionIsBetter) {
|
|
||||||
const sessionEntry = {
|
|
||||||
roomId,
|
|
||||||
senderKey,
|
|
||||||
sessionId,
|
|
||||||
session: session.pickle(this._pickleKey),
|
|
||||||
claimedKeys: {ed25519: claimedEd25519Key},
|
|
||||||
};
|
|
||||||
txn.inboundGroupSessions.set(sessionEntry);
|
|
||||||
newSessions.push(sessionEntry);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
session.free();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// this will be passed to the Room in notifyRoomKeys
|
// this will be passed to the Room in notifyRoomKeys
|
||||||
return newSessions;
|
return newSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
sessionInfo is a response from key backup and has the following keys:
|
||||||
|
algorithm
|
||||||
|
forwarding_curve25519_key_chain
|
||||||
|
sender_claimed_keys
|
||||||
|
sender_key
|
||||||
|
session_key
|
||||||
|
*/
|
||||||
|
async addRoomKeyFromBackup(roomId, sessionId, sessionInfo, txn) {
|
||||||
|
const sessionKey = sessionInfo["session_key"];
|
||||||
|
const senderKey = sessionInfo["sender_key"];
|
||||||
|
const claimedEd25519Key = sessionInfo["sender_claimed_keys"]?.["ed25519"];
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof roomId !== "string" ||
|
||||||
|
typeof sessionId !== "string" ||
|
||||||
|
typeof senderKey !== "string" ||
|
||||||
|
typeof sessionKey !== "string" ||
|
||||||
|
typeof claimedEd25519Key !== "string"
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return await this._writeInboundSession(
|
||||||
|
roomId, senderKey, claimedEd25519Key, sessionId, sessionKey, txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _writeInboundSession(roomId, senderKey, claimedEd25519Key, sessionId, sessionKey, txn) {
|
||||||
|
const session = new this._olm.InboundGroupSession();
|
||||||
|
try {
|
||||||
|
session.create(sessionKey);
|
||||||
|
|
||||||
|
let incomingSessionIsBetter = true;
|
||||||
|
const existingSessionEntry = await txn.inboundGroupSessions.get(roomId, senderKey, sessionId);
|
||||||
|
if (existingSessionEntry) {
|
||||||
|
const existingSession = new this._olm.InboundGroupSession();
|
||||||
|
try {
|
||||||
|
existingSession.unpickle(this._pickleKey, existingSessionEntry.session);
|
||||||
|
incomingSessionIsBetter = session.first_known_index() < existingSession.first_known_index();
|
||||||
|
} finally {
|
||||||
|
existingSession.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incomingSessionIsBetter) {
|
||||||
|
const sessionEntry = {
|
||||||
|
roomId,
|
||||||
|
senderKey,
|
||||||
|
sessionId,
|
||||||
|
session: session.pickle(this._pickleKey),
|
||||||
|
claimedKeys: {ed25519: claimedEd25519Key},
|
||||||
|
};
|
||||||
|
txn.inboundGroupSessions.set(sessionEntry);
|
||||||
|
return sessionEntry;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
session.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in a new issue