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

Check sigs on e2e backup & enable it if we can

This commit is contained in:
David Baker
2018-09-14 17:06:27 +01:00
parent 3838fab788
commit e789747834
3 changed files with 155 additions and 5 deletions

View File

@@ -544,7 +544,15 @@ MatrixClient.prototype.setDeviceVerified = function(userId, deviceId, verified)
if (verified === undefined) { if (verified === undefined) {
verified = true; verified = true;
} }
return _setDeviceVerification(this, userId, deviceId, verified, null); const prom = _setDeviceVerification(this, userId, deviceId, verified, null);
// if one of the user's own devices is being marked as verified / unverified,
// check the key backup status, since whether or not we use this depends on
// whether it has a signature from a verified device
if (userId == this.credentials.userId) {
this._crypto.checkKeyBackup();
}
return prom;
}; };
/** /**
@@ -752,10 +760,6 @@ MatrixClient.prototype.importRoomKeys = function(keys) {
* Get information about the current key backup. * Get information about the current key backup.
*/ */
MatrixClient.prototype.getKeyBackupVersion = function(callback) { MatrixClient.prototype.getKeyBackupVersion = function(callback) {
if (this._crypto === null) {
throw new Error("End-to-end encryption disabled");
}
return this._http.authedRequest( return this._http.authedRequest(
undefined, "GET", "/room_keys/version", undefined, "GET", "/room_keys/version",
).then((res) => { ).then((res) => {
@@ -784,6 +788,20 @@ MatrixClient.prototype.getKeyBackupVersion = function(callback) {
}); });
} }
/**
* @param {object} info key backup info dict from getKeyBackupVersion()
* @return {object} {
* usable: [bool], // is the backup trusted, true iff there is a sig that is valid & from a trusted device
* sigs: [
* valid: [bool],
* device: [DeviceInfo],
* ]
* }
*/
MatrixClient.prototype.isKeyBackupTrusted = function(info) {
return this._crypto.isKeyBackupTrusted(info);
};
/** /**
* @returns {bool} true if the client is configured to back up keys to * @returns {bool} true if the client is configured to back up keys to
* the server, otherwise false. * the server, otherwise false.
@@ -807,6 +825,8 @@ MatrixClient.prototype.enableKeyBackup = function(info) {
this._crypto.backupInfo = info; 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);
this.emit('keyBackupStatus', true);
} }
/** /**
@@ -819,6 +839,8 @@ MatrixClient.prototype.disableKeyBackup = function() {
this._crypto.backupInfo = null; this._crypto.backupInfo = null;
this._crypto.backupKey = null; this._crypto.backupKey = null;
this.emit('keyBackupStatus', false);
} }
/** /**
@@ -3972,6 +3994,18 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
* }); * });
*/ */
/**
* Fires whenever the status of e2e key backup changes, as returned by getKeyBackupEnabled()
* @event module:client~MatrixClient#"keyBackupStatus"
* @param {bool} enabled true if key backup has been enabled, otherwise false
* @example
* matrixClient.on("keyBackupStatus", function(enabled){
* if (enabled) {
* [...]
* }
* });
*/
/** /**
* Fires when we want to suggest to the user that they restore their megolm keys * Fires when we want to suggest to the user that they restore their megolm keys
* from backup or by cross-signing the device. * from backup or by cross-signing the device.

View File

@@ -849,6 +849,8 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
this._retryDecryption(senderKey, sessionId); this._retryDecryption(senderKey, sessionId);
}).then(() => { }).then(() => {
if (this._crypto.backupInfo) { if (this._crypto.backupInfo) {
// XXX: No retries on this at all: if this request dies for whatever
// reason, this key will never be uploaded.
return this._crypto.backupGroupSession( return this._crypto.backupGroupSession(
content.room_id, senderKey, forwardingKeyChain, content.room_id, senderKey, forwardingKeyChain,
content.session_id, content.session_key, keysClaimed, content.session_id, content.session_key, keysClaimed,

View File

@@ -77,6 +77,7 @@ function Crypto(baseApis, sessionStore, userId, deviceId,
// 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.backupInfo = null; // The info dict from /room_keys/version this.backupInfo = null; // The info dict from /room_keys/version
this.backupKey = null; // The encryption key object this.backupKey = null; // The encryption key object
this._checkedForBackup = false; // Have we checked the server for a backup we can use?
this._olmDevice = new OlmDevice(sessionStore, cryptoStore); this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
this._deviceList = new DeviceList( this._deviceList = new DeviceList(
@@ -180,6 +181,113 @@ Crypto.prototype.init = async function() {
); );
this._deviceList.saveIfDirty(); this._deviceList.saveIfDirty();
} }
this._checkAndStartKeyBackup();
};
/**
* Check the server for an active key backup and
* if one is present and has a valid signature from
* one of the user's verified devices, start backing up
* to it.
*/
Crypto.prototype._checkAndStartKeyBackup = async function() {
console.log("Checking key backup status...");
let backupInfo;
try {
backupInfo = await this._baseApis.getKeyBackupVersion();
} catch (e) {
console.log("Error checking for active key backup", e);
if (Number.isFinite(e.httpStatus) && e.httpStatus / 100 === 4) {
// well that's told us. we won't try again.
this._checkedForBackup = true;
}
return;
}
this._checkedForBackup = true;
const trustInfo = await this.isKeyBackupTrusted(backupInfo);
if (trustInfo.usable && !this.backupInfo) {
console.log("Found usable key backup: enabling key backups");
this._baseApis.enableKeyBackup(backupInfo);
} else if (!trustInfo.usable && this.backupInfo) {
console.log("No usable key backup: disabling key backup");
this._baseApis.disableKeyBackup();
} else if (!trustInfo.usable && !this.backupInfo) {
console.log("No usable key backup: not enabling key backup");
}
};
/**
* Forces a re-check of the key backup and enables/disables it
* as appropriate
*/
Crypto.prototype.checkKeyBackup = async function(backupInfo) {
this._checkedForBackup = false;
await this._checkAndStartKeyBackup();
};
/**
* @param {object} backupInfo key backup info dict from /room_keys/version
* @return {object} {
* usable: [bool], // is the backup trusted, true iff there is a sig that is valid & from a trusted device
* sigs: [
* valid: [bool],
* device: [DeviceInfo],
* ]
* }
*/
Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) {
const ret = {
usable: false,
sigs: [],
};
if (
!backupInfo ||
!backupInfo.algorithm ||
!backupInfo.auth_data ||
!backupInfo.auth_data.public_key ||
!backupInfo.auth_data.signatures
) {
console.log("Key backup is absent or missing required data");
return ret;
}
const mySigs = backupInfo.auth_data.signatures[this._userId];
if (!mySigs || mySigs.length === 0) {
console.log("Ignoring key backup because it lacks any signatures from this user");
return ret;
}
for (const keyId of Object.keys(mySigs)) {
const device = this._deviceList.getStoredDevice(
this._userId, keyId.split(':')[1], // XXX: is this how we're supposed to get the device ID?
);
if (!device) {
console.log("Ignoring signature from unknown key " + keyId);
continue;
}
const sigInfo = { device };
try {
await olmlib.verifySignature(
this._olmDevice,
backupInfo.auth_data,
this._userId,
device.deviceId,
device.getFingerprint(),
);
sigInfo.valid = true;
} catch (e) {
console.log("Bad signature from device " + device.deviceId, e);
sigInfo.valid = false;
}
ret.sigs.push(sigInfo);
}
ret.usable = ret.sigs.some(s => s.valid && s.device.isVerified());
return ret;
}; };
/** /**
@@ -1233,6 +1341,12 @@ Crypto.prototype._onRoomKeyEvent = function(event) {
return; return;
} }
if (!this._checkedForBackup) {
// don't bother awaiting on this - the important thing is that we retry if we
// haven't managed to check before
this._checkAndStartKeyBackup();
}
const alg = this._getRoomDecryptor(content.room_id, content.algorithm); const alg = this._getRoomDecryptor(content.room_id, content.algorithm);
alg.onRoomKeyEvent(event); alg.onRoomKeyEvent(event);
}; };