You've already forked matrix-js-sdk
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:
@@ -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);
|
||||||
|
|||||||
81
src/crypto/backup_password.js
Normal file
81
src/crypto/backup_password.js
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user