You've already forked matrix-js-sdk
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:
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user