1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-29 16:43:09 +03:00

Support passphrase-based e2e key backups

This commit is contained in:
David Baker
2018-11-20 13:09:59 +00:00
parent d99a22d68d
commit 44d99277fe
2 changed files with 123 additions and 8 deletions

View File

@@ -49,6 +49,7 @@ import RoomList from './crypto/RoomList';
import Crypto from './crypto'; import Crypto from './crypto';
import { isCryptoAvailable } from './crypto'; import { isCryptoAvailable } from './crypto';
import { encodeRecoveryKey, decodeRecoveryKey } from './crypto/recoverykey'; import { encodeRecoveryKey, decodeRecoveryKey } from './crypto/recoverykey';
import { keyForNewBackup, keyForExistingBackup } from './crypto/backup_password';
// Disable warnings for now: we use deprecated bluebird functions // Disable warnings for now: we use deprecated bluebird functions
// and need to migrate, but they spam the console with warnings. // and need to migrate, but they spam the console with warnings.
@@ -860,22 +861,37 @@ MatrixClient.prototype.disableKeyBackup = function() {
* Set up the data required to create a new backup version. The backup version * Set up the data required to create a new backup version. The backup version
* will not be created and enabled until createKeyBackupVersion is called. * will not be created and enabled until createKeyBackupVersion is called.
* *
* @param {string} password Passphrase string that can be entered by the user
* when restoring the backup as an alternative to entering the recovery key.
* Optional.
*
* @returns {object} Object that can be passed to createKeyBackupVersion and * @returns {object} Object that can be passed to createKeyBackupVersion and
* additionally has a 'recovery_key' member with the user-facing recovery key string. * additionally has a 'recovery_key' member with the user-facing recovery key string.
*/ */
MatrixClient.prototype.prepareKeyBackupVersion = function() { MatrixClient.prototype.prepareKeyBackupVersion = async function(password) {
if (this._crypto === null) { if (this._crypto === null) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
const decryption = new global.Olm.PkDecryption(); const decryption = new global.Olm.PkDecryption();
try { try {
const publicKey = decryption.generate_key(); let privateKey;
let publicKey;
let authData = {};
if (password) {
const keyInfo = await keyForNewBackup(password);
publicKey = decryption.init_with_private_key(keyInfo.key);
authData.private_key_salt = keyInfo.salt;
authData.private_key_iterations = keyInfo.iterations;
} else {
publicKey = decryption.generate_key();
}
authData.public_key = publicKey;
return { return {
algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM,
auth_data: { auth_data: authData,
public_key: publicKey,
},
recovery_key: encodeRecoveryKey(decryption.get_private_key()), recovery_key: encodeRecoveryKey(decryption.get_private_key()),
}; };
} finally { } finally {
@@ -992,8 +1008,28 @@ MatrixClient.prototype.isValidRecoveryKey = function(recoveryKey) {
} }
}; };
MatrixClient.prototype.restoreKeyBackups = function( MatrixClient.prototype.restoreKeyBackupWithPassword = async function(
password, targetRoomId, targetSessionId, version,
) {
const backupInfo = await this.getKeyBackupVersion();
const privKey = keyForExistingBackup(backupInfo, password);
return this._restoreKeyBackup(
privKey, targetRoomId, targetSessionId, version,
);
};
MatrixClient.prototype.restoreKeyBackupWithRecoveryKey = function(
recoveryKey, targetRoomId, targetSessionId, version, recoveryKey, targetRoomId, targetSessionId, version,
) {
const privKey = decodeRecoveryKey(recoveryKey);
return this._restoreKeyBackup(
privKey, targetRoomId, targetSessionId, version,
);
};
MatrixClient.prototype._restoreKeyBackup = function(
privKey, targetRoomId, targetSessionId, version,
) { ) {
if (this._crypto === null) { if (this._crypto === null) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
@@ -1003,8 +1039,6 @@ MatrixClient.prototype.restoreKeyBackups = function(
const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, version); const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, version);
// FIXME: see the FIXME in createKeyBackupVersion
const privkey = decodeRecoveryKey(recoveryKey);
const decryption = new global.Olm.PkDecryption(); const decryption = new global.Olm.PkDecryption();
try { try {
decryption.init_with_private_key(privkey); decryption.init_with_private_key(privkey);

View File

@@ -0,0 +1,81 @@
/*
Copyright 2018 New Vector Ltd
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.
*/
import { randomString } from '../randomstring';
const DEFAULT_ITERATIONS = 500000;
export async function keyForExistingBackup(backupData, password) {
if (!global.Olm) {
throw new Error("Olm is not available");
}
const authData = backupData.auth_data;
if (!authData.private_key_salt || !authData.private_key_iterations) {
throw new Error(
"Salt and/or iterations not found: " +
"this backup cannot be restored with a passphrase",
);
}
return await deriveKey(
password, backupData.private_key_salt,
backupData.private_key_iterations,
);
}
export async function keyForNewBackup(password) {
if (!global.Olm) {
throw new Error("Olm is not available");
}
const salt = randomString(32);
const key = await deriveKey(password, salt, DEFAULT_ITERATIONS);
return { key, salt, iterations: DEFAULT_ITERATIONS };
}
async function deriveKey(password, salt, iterations) {
const subtleCrypto = global.crypto.subtle;
const TextEncoder = global.TextEncoder;
if (!subtleCrypto || !TextEncoder) {
// TODO: Implement this for node
throw new Error("Password-based backup is not avaiable on this platform");
}
const key = await subtleCrypto.importKey(
'raw',
new TextEncoder().encode(password),
{name: 'PBKDF2'},
false,
['deriveBits'],
);
const keybits = await subtleCrypto.deriveBits(
{
name: 'PBKDF2',
salt: new TextEncoder().encode(salt),
iterations: iterations,
hash: 'SHA-512',
},
key,
global.Olm.PRIVATE_KEY_LENGTH * 8,
);
return new Uint8Array(keybits);
}