diff --git a/src/crypto/SecretStorage.js b/src/crypto/SecretStorage.js index db984b950..36aa59cf3 100644 --- a/src/crypto/SecretStorage.js +++ b/src/crypto/SecretStorage.js @@ -231,6 +231,27 @@ export default class SecretStorage extends EventEmitter { await this._baseApis.setAccountData(name, {encrypted}); } + /** + * Store a secret defined to be the same as the given key. + * No secret information will be stored, instead the secret will + * be stored with a marker to say that the contents of the secret is + * the value of the given key. + * This is useful for migration from systems that predate SSSS such as + * key backup. + * + * @param {string} name The name of the secret + * @param {string} keyId The ID of the key whose value will be the + * value of the secret + * @returns {Promise} resolved when account data is saved + */ + storePassthrough(name, keyId) { + return this._baseApis.setAccountData(name, { + [keyId]: { + passthrough: true, + }, + }); + } + /** * Get a secret from storage. * @@ -276,8 +297,13 @@ export default class SecretStorage extends EventEmitter { // fetch private key from app [keyId, decryption] = await this._getSecretStorageKey(keys); - // decrypt secret const encInfo = secretContent.encrypted[keyId]; + + // We don't actually need the decryption object if it's a passthrough + // since we just want to return the key itself. + if (encInfo.passthrough) return decryption.get_private_key(); + + // decrypt secret switch (keys[keyId].algorithm) { case SECRET_STORAGE_ALGORITHM_V1: return decryption.decrypt( diff --git a/src/crypto/index.js b/src/crypto/index.js index badf89013..fa547c395 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -336,6 +336,8 @@ Crypto.prototype.createRecoveryKeyFromPassphrase = async function(password) { * auth data as an object. * @param {function} [opts.createSecretStorageKey] Optional. Function * called to await a secret storage key creation flow. + * @param {object} [opts.keyBackupInfo] The current key backup object. If passed, + * the passphrase and recovery key from this backup will be used. * Returns: * {Promise} A promise which resolves to key creation data for * SecretStorage#addKey: an object with `passphrase` and/or `pubkey` fields. @@ -343,6 +345,7 @@ Crypto.prototype.createRecoveryKeyFromPassphrase = async function(password) { Crypto.prototype.bootstrapSecretStorage = async function({ authUploadDeviceSigningKeys, createSecretStorageKey = async () => { }, + keyBackupInfo, } = {}) { logger.log("Bootstrapping Secure Secret Storage"); @@ -383,18 +386,49 @@ Crypto.prototype.bootstrapSecretStorage = async function({ { authUploadDeviceSigningKeys }, ); } + } else { + logger.log("Cross signing keys are present in secret storage"); } // Check if Secure Secret Storage has a default key. If we don't have one, create // the default key (which will also be signed by the cross-signing master key). if (!this.hasSecretStorageKey()) { - logger.log("Secret storage default key not found, creating new key"); - const keyOptions = await createSecretStorageKey(); - const newKeyId = await this.addSecretStorageKey( - SECRET_STORAGE_ALGORITHM_V1, - keyOptions, - ); + let newKeyId; + if (keyBackupInfo) { + logger.log("Secret storage default key not found, using key backup key"); + const opts = { + pubkey: keyBackupInfo.auth_data.public_key, + }; + + if ( + keyBackupInfo.auth_data.private_key_salt && + keyBackupInfo.auth_data.private_key_iterations + ) { + opts.passphrase = { + algorithm: "m.pbkdf2", + iterations: keyBackupInfo.auth_data.private_key_iterations, + salt: keyBackupInfo.auth_data.private_key_salt, + }; + } + + newKeyId = await this.addSecretStorageKey( + SECRET_STORAGE_ALGORITHM_V1, opts, + ); + + // Add an entry for the backup key in SSSS as a 'passthrough' key + // (ie. the secret is the key itself). + this._secretStorage.storePassthrough('m.megolm_backup.v1', newKeyId); + } else { + logger.log("Secret storage default key not found, creating new key"); + const keyOptions = await createSecretStorageKey(); + newKeyId = await this.addSecretStorageKey( + SECRET_STORAGE_ALGORITHM_V1, + keyOptions, + ); + } await this.setDefaultSecretStorageKeyId(newKeyId); + } else { + logger.log("Have secret storage key"); } // If cross-signing keys were reset, store them in Secure Secret Storage.