1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-12-07 05:22:15 +03:00

Store SSK & USK in crypto store

and restore them from the key backup.

NB. This has an interface change to restoreKeyBackup where I've
changed it to take a backupInfo rather than a version (this also
saves us re-fetching the backup metadata in the case of a passphrase
restore).
This commit is contained in:
David Baker
2019-01-31 15:48:05 +00:00
parent 2b54f442d1
commit 02d4dcb128
5 changed files with 130 additions and 21 deletions

View File

@@ -53,6 +53,8 @@ import { keyForNewBackup, keyForExistingBackup } from './crypto/backup_password'
import { randomString } from './randomstring';
import { pkSign } from './crypto/PkSigning';
import IndexedDBCryptoStore from './crypto/store/indexeddb-crypto-store';
// Disable warnings for now: we use deprecated bluebird functions
// and need to migrate, but they spam the console with warnings.
Promise.config({warnings: false});
@@ -982,24 +984,40 @@ MatrixClient.prototype.prepareKeyBackupVersion = async function(password) {
algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM,
auth_data: authData,
recovery_key: encodeRecoveryKey(decryption.get_private_key()),
accountKeys: null,
};
if (signing) {
const ssk_seed = signing.generate_seed();
await this._cryptoStore.doTxn('readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
this._cryptoStore.getAccountKeys(txn, keys => {
returnInfo.accountKeys = keys;
});
});
if (!returnInfo.accountKeys) {
const ssk_seed = signing.generate_seed();
const usk_seed = signing.generate_seed();
returnInfo.accountKeys = {
self_signing_key_seed: Buffer.from(ssk_seed).toString('base64'),
user_signing_key_seed: Buffer.from(usk_seed).toString('base64'),
}
}
// put the encrypted version of the seed in the auth data to upload
// XXX: our encryption really should support encrypting binary data.
authData.self_signing_key = encryption.encrypt(Buffer.from(ssk_seed).toString('base64'));
// and the unencrypted one in the returndata so we can use it later
returnInfo.ssk_seed = ssk_seed
authData.self_signing_key_seed = encryption.encrypt(returnInfo.accountKeys.self_signing_key_seed);
// also keep the public part there
returnInfo.ssk_public = signing.init_with_seed(ssk_seed);
returnInfo.ssk_public = signing.init_with_seed(Buffer.from(returnInfo.accountKeys.self_signing_key_seed, 'base64'));
signing.free();
const usk_seed = signing.generate_seed();
authData.user_signing_key = encryption.encrypt(Buffer.from(usk_seed).toString('base64'));
returnInfo.usk_seed = usk_seed;
returnInfo.usk_public = signing.init_with_seed(usk_seed);
// same for the USK
authData.user_signing_key_seed = encryption.encrypt(returnInfo.accountKeys.user_signing_key_seed);
returnInfo.usk_public = signing.init_with_seed(Buffer.from(returnInfo.accountKeys.user_signing_key_seed, 'base64'));
signing.free();
// we don't save these keys back to the store yet: we'll do that when (if) we
// actually create the backup
}
return returnInfo;
@@ -1035,7 +1053,8 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, auth, replacesSsk
},
};
pkSign(uskInfo, info.ssk_seed, this.credentials.userId);
// sign the USK with the SSK
pkSign(uskInfo, Buffer.from(info.accountKeys.self_signing_key_seed, 'base64'), this.credentials.userId);
const keys = {
self_signing_key: {
@@ -1050,7 +1069,11 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, auth, replacesSsk
auth,
};
return this.uploadDeviceSigningKeys(keys).then(() => {
return this._cryptoStore.doTxn('readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
this._cryptoStore.storeAccountKeys(txn, info.accountKeys);
}).then(() => {
return this.uploadDeviceSigningKeys(keys);
}).then(() => {
return this._crypto._signObject(data.auth_data);
}).then(() => {
return this._http.authedRequest(
@@ -1150,27 +1173,25 @@ MatrixClient.prototype.isValidRecoveryKey = function(recoveryKey) {
};
MatrixClient.prototype.restoreKeyBackupWithPassword = async function(
password, targetRoomId, targetSessionId, version,
password, targetRoomId, targetSessionId, backupInfo,
) {
const backupInfo = await this.getKeyBackupVersion();
const privKey = await keyForExistingBackup(backupInfo, password);
return this._restoreKeyBackup(
privKey, targetRoomId, targetSessionId, version,
privKey, targetRoomId, targetSessionId, backupInfo,
);
};
MatrixClient.prototype.restoreKeyBackupWithRecoveryKey = function(
recoveryKey, targetRoomId, targetSessionId, version,
recoveryKey, targetRoomId, targetSessionId, backupInfo,
) {
const privKey = decodeRecoveryKey(recoveryKey);
return this._restoreKeyBackup(
privKey, targetRoomId, targetSessionId, version,
privKey, targetRoomId, targetSessionId, backupInfo,
);
};
MatrixClient.prototype._restoreKeyBackup = function(
privKey, targetRoomId, targetSessionId, version,
MatrixClient.prototype._restoreKeyBackup = async function(
privKey, targetRoomId, targetSessionId, backupInfo,
) {
if (this._crypto === null) {
throw new Error("End-to-end encryption disabled");
@@ -1178,11 +1199,39 @@ MatrixClient.prototype._restoreKeyBackup = function(
let totalKeyCount = 0;
let keys = [];
const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, version);
const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, backupInfo.version);
const decryption = new global.Olm.PkDecryption();
try {
decryption.init_with_private_key(privKey);
// decrypt the account keys from the backup info if there are any
// fetch the old ones first so we don't lose info if only one of them is in the backup
let accountKeys;
await this._cryptoStore.doTxn('readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
this._cryptoStore.getAccountKeys(txn, keys => {
accountKeys = keys || {};
});
});
if (backupInfo.auth_data.self_signing_key_seed) {
accountKeys.self_signing_key_seed = decryption.decrypt(
backupInfo.auth_data.self_signing_key_seed.ephemeral,
backupInfo.auth_data.self_signing_key_seed.mac,
backupInfo.auth_data.self_signing_key_seed.ciphertext,
);
}
if (backupInfo.auth_data.user_signing_key_seed) {
accountKeys.user_signing_key_seed = decryption.decrypt(
backupInfo.auth_data.user_signing_key_seed.ephemeral,
backupInfo.auth_data.user_signing_key_seed.mac,
backupInfo.auth_data.user_signing_key_seed.ciphertext,
);
}
await this._cryptoStore.doTxn('readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
this._cryptoStore.storeAccountKeys(txn, accountKeys);
});
} catch(e) {
decryption.free();
throw e;

View File

@@ -332,6 +332,23 @@ export class Backend {
objectStore.put(newData, "-");
}
getAccountKeys(txn, func) {
const objectStore = txn.objectStore("account");
const getReq = objectStore.get("keys");
getReq.onsuccess = function() {
try {
func(getReq.result || null);
} catch (e) {
abortWithException(txn, e);
}
};
}
storeAccountKeys(txn, keys) {
const objectStore = txn.objectStore("account");
objectStore.put(keys, "keys");
}
// Olm Sessions
countEndToEndSessions(txn, func) {

View File

@@ -283,7 +283,7 @@ export default class IndexedDBCryptoStore {
this._backendPromise.value().getAccount(txn, func);
}
/*
/**
* Write the account pickle to the store.
* This requires an active transaction. See doTxn().
*
@@ -294,6 +294,28 @@ export default class IndexedDBCryptoStore {
this._backendPromise.value().storeAccount(txn, newData);
}
/**
* Get the account keys fort cross-signing (eg. self-signing key,
* user signing key).
*
* @param {*} txn An active transaction. See doTxn().
* @param {function(string)} func Called with the account keys object:
* { key_type: base64 encoded seed } where key type = user_signing_key_seed or self_signing_key_seed
*/
getAccountKeys(txn, func) {
this._backendPromise.value().getAccountKeys(txn, func);
}
/**
* Write the account keys back to the store
*
* @param {*} txn An active transaction. See doTxn().
* @param {string} keys Account keys object as getAccountKeys()
*/
storeAccountKeys(txn, keys) {
this._backendPromise.value().storeAccountKeys(txn, keys);
}
// Olm sessions
/**

View File

@@ -31,6 +31,7 @@ import MemoryCryptoStore from './memory-crypto-store.js';
const E2E_PREFIX = "crypto.";
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
const KEY_END_TO_END_ACCOUNT_KEYS = E2E_PREFIX + "account_keys";
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/";
@@ -274,6 +275,17 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
);
}
getAccountKeys(txn, func) {
const keys = getJsonItem(this.store, KEY_END_TO_END_ACCOUNT_KEYS);
func(keys);
}
storeAccountKeys(txn, keys) {
setJsonItem(
this.store, KEY_END_TO_END_ACCOUNT_KEYS, keys,
);
}
doTxn(mode, stores, func) {
return Promise.resolve(func(null));
}

View File

@@ -33,6 +33,7 @@ export default class MemoryCryptoStore {
constructor() {
this._outgoingRoomKeyRequests = [];
this._account = null;
this._accountKeys = null;
// Map of {devicekey -> {sessionId -> session pickle}}
this._sessions = {};
@@ -234,6 +235,14 @@ export default class MemoryCryptoStore {
this._account = newData;
}
getAccountKeys(txn, func) {
func(this._accountKeys);
}
storeAccountKeys(txn, keys) {
this._accountKeys = keys;
}
// Olm Sessions
countEndToEndSessions(txn, func) {