1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-26 17:03:12 +03:00

WIP e2e key backup support

Continues from uhoreg's branch
This commit is contained in:
David Baker
2018-09-13 17:01:05 +01:00
parent 72bd51f26e
commit 3838fab788
3 changed files with 155 additions and 61 deletions

View File

@@ -774,9 +774,27 @@ MatrixClient.prototype.getKeyBackupVersion = function(callback) {
} }
return res; return res;
} }
}).catch(e => {
if (e.errcode === 'M_NOT_FOUND') {
if (callback) callback(null);
return null;
} else {
throw e;
}
}); });
} }
/**
* @returns {bool} true if the client is configured to back up keys to
* the server, otherwise false.
*/
MatrixClient.prototype.getKeyBackupEnabled = function() {
if (this._crypto === null) {
throw new Error("End-to-end encryption disabled");
}
return Boolean(this._crypto.backupKey);
}
/** /**
* Enable backing up of keys, using data previously returned from * Enable backing up of keys, using data previously returned from
* getKeyBackupVersion. * getKeyBackupVersion.
@@ -786,6 +804,7 @@ MatrixClient.prototype.enableKeyBackup = function(info) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
this._crypto.backupInfo = info;
this._crypto.backupKey = new global.Olm.PkEncryption(); this._crypto.backupKey = new global.Olm.PkEncryption();
this._crypto.backupKey.set_recipient_key(info.auth_data.public_key); this._crypto.backupKey.set_recipient_key(info.auth_data.public_key);
} }
@@ -798,7 +817,8 @@ MatrixClient.prototype.disableKeyBackup = function() {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
this._crypto.backupKey = undefined; this._crypto.backupInfo = null;
this._crypto.backupKey = null;
} }
/** /**
@@ -836,11 +856,16 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, callback) {
algorithm: info.algorithm, algorithm: info.algorithm,
auth_data: info.auth_data, // FIXME: should this be cloned? auth_data: info.auth_data, // FIXME: should this be cloned?
} }
this._crypto._signObject(data.auth_data); return this._crypto._signObject(data.auth_data).then(() => {
return this._http.authedRequest( return this._http.authedRequest(
undefined, "POST", "/room_keys/version", undefined, data, undefined, "POST", "/room_keys/version", undefined, data,
).then((res) => { );
this.enableKeyBackup(info); }).then((res) => {
this.enableKeyBackup({
algorithm: info.algorithm,
auth_data: info.auth_data,
version: res.version,
});
if (callback) { if (callback) {
callback(null, res); callback(null, res);
} }
@@ -848,6 +873,27 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, callback) {
}); });
} }
MatrixClient.prototype.deleteKeyBackupVersion = function(version) {
if (this._crypto === null) {
throw new Error("End-to-end encryption disabled");
}
// If we're currently backing up to this backup... stop.
// (We start using it automatically in createKeyBackupVersion
// so this is symmetrical).
if (this._crypto.backupInfo && this._crypto.backupInfo.version === version) {
this.disableKeyBackup();
}
const path = utils.encodeUri("/room_keys/version/$version", {
$version: version,
});
return this._http.authedRequest(
undefined, "DELETE", path, undefined, undefined,
);
};
MatrixClient.prototype._makeKeyBackupPath = function(roomId, sessionId, version) { MatrixClient.prototype._makeKeyBackupPath = function(roomId, sessionId, version) {
let path; let path;
if (sessionId !== undefined) { if (sessionId !== undefined) {
@@ -890,6 +936,14 @@ MatrixClient.prototype.sendKeyBackup = function(roomId, sessionId, version, data
); );
}; };
MatrixClient.prototype.backupAllGroupSessions = function(version) {
if (this._crypto === null) {
throw new Error("End-to-end encryption disabled");
}
return this._crypto.backupAllGroupSessions(version);
};
MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessionId, version, callback) { MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessionId, version, callback) {
if (this._crypto === null) { if (this._crypto === null) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
@@ -924,7 +978,7 @@ MatrixClient.prototype.restoreKeyBackups = function(decryptionKey, roomId, sessi
}) })
}; };
MatrixClient.prototype.deleteKeyBackups = function(roomId, sessionId, version, callback) { MatrixClient.prototype.deleteKeysFromBackup = function(roomId, sessionId, version, callback) {
if (this._crypto === null) { if (this._crypto === null) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }

View File

@@ -263,6 +263,14 @@ MegolmEncryption.prototype._prepareNewSession = async function() {
key.key, {ed25519: this._olmDevice.deviceEd25519Key}, key.key, {ed25519: this._olmDevice.deviceEd25519Key},
); );
if (this._crypto.backupInfo) {
// Not strictly necessary to wait for this
await this._crypto.backupGroupSession(
this._roomId, this._olmDevice.deviceCurve25519Key, [],
sessionId, key.key,
);
}
return new OutboundSessionInfo(sessionId); return new OutboundSessionInfo(sessionId);
}; };
@@ -840,11 +848,13 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
// have another go at decrypting events sent with this session. // have another go at decrypting events sent with this session.
this._retryDecryption(senderKey, sessionId); this._retryDecryption(senderKey, sessionId);
}).then(() => { }).then(() => {
return this.backupGroupSession( if (this._crypto.backupInfo) {
content.room_id, senderKey, forwardingKeyChain, return this._crypto.backupGroupSession(
content.session_id, content.session_key, keysClaimed, content.room_id, senderKey, forwardingKeyChain,
exportFormat, content.session_id, content.session_key, keysClaimed,
); exportFormat,
);
}
}).catch((e) => { }).catch((e) => {
console.error(`Error handling m.room_key_event: ${e}`); console.error(`Error handling m.room_key_event: ${e}`);
}); });
@@ -967,54 +977,6 @@ MegolmDecryption.prototype.importRoomKey = function(session) {
}); });
}; };
MegolmDecryption.prototype.backupGroupSession = async function(
roomId, senderKey, forwardingCurve25519KeyChain,
sessionId, sessionKey, keysClaimed,
exportFormat,
) {
// new session.
const session = new Olm.InboundGroupSession();
let first_known_index;
try {
if (exportFormat) {
session.import_session(sessionKey);
} else {
session.create(sessionKey);
}
if (sessionId != session.session_id()) {
throw new Error(
"Mismatched group session ID from senderKey: " +
senderKey,
);
}
if (!exportFormat) {
sessionKey = session.export_session();
}
const first_known_index = session.first_known_index();
const sessionData = {
algorithm: olmlib.MEGOLM_ALGORITHM,
sender_key: senderKey,
sender_claimed_keys: keysClaimed,
forwardingCurve25519KeyChain: forwardingCurve25519KeyChain,
session_key: sessionKey
};
const encrypted = this._crypto.backupKey.encrypt(JSON.stringify(sessionData));
const data = {
first_message_index: first_known_index,
forwarded_count: forwardingCurve25519KeyChain.length,
is_verified: false, // FIXME: how do we determine this?
session_data: encrypted
};
return this._baseApis.sendKeyBackup(roomId, sessionId, data);
} catch (e) {
return Promise.reject(e);
} finally {
session.free();
}
}
/** /**
* Have another go at decrypting events after we receive a key * Have another go at decrypting events after we receive a key
* *

View File

@@ -75,7 +75,8 @@ function Crypto(baseApis, sessionStore, userId, deviceId,
// track whether this device's megolm keys are being backed up incrementally // track whether this device's megolm keys are being backed up incrementally
// to the server or not. // to the server or not.
// XXX: this should probably have a single source of truth from OlmAccount // XXX: this should probably have a single source of truth from OlmAccount
this.backupKey = null; this.backupInfo = null; // The info dict from /room_keys/version
this.backupKey = null; // The encryption key object
this._olmDevice = new OlmDevice(sessionStore, cryptoStore); this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
this._deviceList = new DeviceList( this._deviceList = new DeviceList(
@@ -848,6 +849,83 @@ Crypto.prototype.importRoomKeys = function(keys) {
}, },
); );
}; };
Crypto.prototype._backupPayloadForSession = function(
senderKey, forwardingCurve25519KeyChain,
sessionId, sessionKey, keysClaimed,
exportFormat,
) {
// new session.
const session = new Olm.InboundGroupSession();
let first_known_index;
try {
if (exportFormat) {
session.import_session(sessionKey);
} else {
session.create(sessionKey);
}
if (sessionId != session.session_id()) {
throw new Error(
"Mismatched group session ID from senderKey: " +
senderKey,
);
}
if (!exportFormat) {
sessionKey = session.export_session();
}
const first_known_index = session.first_known_index();
const sessionData = {
algorithm: olmlib.MEGOLM_ALGORITHM,
sender_key: senderKey,
sender_claimed_keys: keysClaimed,
session_key: sessionKey,
forwarding_curve25519_key_chain: forwardingCurve25519KeyChain,
};
const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData));
return {
first_message_index: first_known_index,
forwarded_count: forwardingCurve25519KeyChain.length,
is_verified: false, // FIXME: how do we determine this?
session_data: encrypted,
};
} finally {
session.free();
}
};
Crypto.prototype.backupGroupSession = function(
roomId, senderKey, forwardingCurve25519KeyChain,
sessionId, sessionKey, keysClaimed,
exportFormat,
) {
if (!this.backupInfo) {
throw new Error("Key backups are not enabled");
}
const data = this._backupPayloadForSession(
senderKey, forwardingCurve25519KeyChain,
sessionId, sessionKey, keysClaimed,
exportFormat,
);
return this._baseApis.sendKeyBackup(roomId, sessionId, this.backupInfo.version, data);
};
Crypto.prototype.backupAllGroupSessions = async function(version) {
const keys = await this.exportRoomKeys();
const data = {};
for (const key of keys) {
if (data[key.room_id] === undefined) data[key.room_id] = {sessions: {}};
data[key.room_id]['sessions'][key.session_id] = this._backupPayloadForSession(
key.sender_key, key.forwarding_curve25519_key_chain,
key.session_id, key.session_key, key.sender_claimed_keys, true,
);
}
return this._baseApis.sendKeyBackup(undefined, undefined, version, {rooms: data});
};
/* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307 /* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307
/** /**
* Encrypt an event according to the configuration of the room. * Encrypt an event according to the configuration of the room.