From 83a8a0cf2125be9573f03d48b25d3ebe0cc93730 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 19 Dec 2019 11:23:57 +0000 Subject: [PATCH 1/5] Add support for passthrough SSSS secrets So we can migrate key backup keys Adding a passthrough secret itself isn't exposed outside of the js-sdk: hopefully this should only ever be necessary for this bootstrap process which the js-sdk handles. --- src/crypto/SecretStorage.js | 31 +++++++++++++++++++++++++--- src/crypto/index.js | 40 +++++++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/crypto/SecretStorage.js b/src/crypto/SecretStorage.js index db984b950..f557c6383 100644 --- a/src/crypto/SecretStorage.js +++ b/src/crypto/SecretStorage.js @@ -231,6 +231,26 @@ 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 + */ + storePassthrough(name, keyId) { + return this._baseApis.setAccountData(name, { + [keyId]: { + passthrough: true, + } + }); + } + /** * Get a secret from storage. * @@ -273,11 +293,14 @@ export default class SecretStorage extends EventEmitter { let keyId; let decryption; try { + const encInfo = secretContent.encrypted[keyId]; + // fetch private key from app - [keyId, decryption] = await this._getSecretStorageKey(keys); + [keyId, decryption] = await this._getSecretStorageKey(keys, encInfo.passthrough); + + if (encInfo.passthrough) return decryption; // decrypt secret - const encInfo = secretContent.encrypted[keyId]; switch (keys[keyId].algorithm) { case SECRET_STORAGE_ALGORITHM_V1: return decryption.decrypt( @@ -518,7 +541,7 @@ export default class SecretStorage extends EventEmitter { } } - async _getSecretStorageKey(keys) { + async _getSecretStorageKey(keys, raw) { if (!this._cryptoCallbacks.getSecretStorageKey) { throw new Error("No getSecretStorageKey callback supplied"); } @@ -537,6 +560,8 @@ export default class SecretStorage extends EventEmitter { throw new Error("App returned unknown key from getSecretStorageKey!"); } + if (raw) return [keyId, privateKey]; + switch (keys[keyId].algorithm) { case SECRET_STORAGE_ALGORITHM_V1: { diff --git a/src/crypto/index.js b/src/crypto/index.js index badf89013..817db2ac0 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"); @@ -388,12 +391,37 @@ Crypto.prototype.bootstrapSecretStorage = async function({ // 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 cli.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); } From 8f7ed1dc15ea78afc14b89e6cec84517d26a9ab6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 19 Dec 2019 11:50:25 +0000 Subject: [PATCH 2/5] Lint --- src/crypto/SecretStorage.js | 7 +++++-- src/crypto/index.js | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/crypto/SecretStorage.js b/src/crypto/SecretStorage.js index f557c6383..f408d8a2d 100644 --- a/src/crypto/SecretStorage.js +++ b/src/crypto/SecretStorage.js @@ -242,12 +242,13 @@ export default class SecretStorage extends EventEmitter { * @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, - } + }, }); } @@ -296,7 +297,9 @@ export default class SecretStorage extends EventEmitter { const encInfo = secretContent.encrypted[keyId]; // fetch private key from app - [keyId, decryption] = await this._getSecretStorageKey(keys, encInfo.passthrough); + [keyId, decryption] = await this._getSecretStorageKey( + keys, encInfo.passthrough, + ); if (encInfo.passthrough) return decryption; diff --git a/src/crypto/index.js b/src/crypto/index.js index 817db2ac0..db891fafd 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -409,7 +409,9 @@ Crypto.prototype.bootstrapSecretStorage = async function({ }; } - newKeyId = await cli.addSecretStorageKey(SECRET_STORAGE_ALGORITHM_V1, opts); + 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). From 41b763f33182a7fd62cfebecca49c533bd7e935c Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 19 Dec 2019 17:25:28 +0000 Subject: [PATCH 3/5] Just get the private key from the decryption object --- src/crypto/SecretStorage.js | 15 ++++++--------- src/crypto/index.js | 4 ++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/crypto/SecretStorage.js b/src/crypto/SecretStorage.js index f408d8a2d..52dbdee2c 100644 --- a/src/crypto/SecretStorage.js +++ b/src/crypto/SecretStorage.js @@ -294,14 +294,12 @@ export default class SecretStorage extends EventEmitter { let keyId; let decryption; try { + // fetch private key from app + [keyId, decryption] = await this._getSecretStorageKey(keys); + const encInfo = secretContent.encrypted[keyId]; - // fetch private key from app - [keyId, decryption] = await this._getSecretStorageKey( - keys, encInfo.passthrough, - ); - - if (encInfo.passthrough) return decryption; + if (encInfo.passthrough) return decryption.get_private_key();; // decrypt secret switch (keys[keyId].algorithm) { @@ -544,7 +542,7 @@ export default class SecretStorage extends EventEmitter { } } - async _getSecretStorageKey(keys, raw) { + async _getSecretStorageKey(keys) { if (!this._cryptoCallbacks.getSecretStorageKey) { throw new Error("No getSecretStorageKey callback supplied"); } @@ -563,8 +561,6 @@ export default class SecretStorage extends EventEmitter { throw new Error("App returned unknown key from getSecretStorageKey!"); } - if (raw) return [keyId, privateKey]; - switch (keys[keyId].algorithm) { case SECRET_STORAGE_ALGORITHM_V1: { @@ -587,5 +583,6 @@ export default class SecretStorage extends EventEmitter { default: throw new Error("Unknown key type: " + keys[keyId].algorithm); } +p } } diff --git a/src/crypto/index.js b/src/crypto/index.js index db891fafd..fa547c395 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -386,6 +386,8 @@ 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 @@ -425,6 +427,8 @@ Crypto.prototype.bootstrapSecretStorage = async function({ ); } await this.setDefaultSecretStorageKeyId(newKeyId); + } else { + logger.log("Have secret storage key"); } // If cross-signing keys were reset, store them in Secure Secret Storage. From 1816d7aa4cb031992c7ba99356a48ea03faac48b Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 19 Dec 2019 17:27:15 +0000 Subject: [PATCH 4/5] comment --- src/crypto/SecretStorage.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/crypto/SecretStorage.js b/src/crypto/SecretStorage.js index 52dbdee2c..7fc978d4d 100644 --- a/src/crypto/SecretStorage.js +++ b/src/crypto/SecretStorage.js @@ -299,7 +299,9 @@ export default class SecretStorage extends EventEmitter { const encInfo = secretContent.encrypted[keyId]; - if (encInfo.passthrough) return decryption.get_private_key();; + // 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) { From 320ab050fe47dbcd405e7bbcda90e8aac13572f8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 19 Dec 2019 17:28:07 +0000 Subject: [PATCH 5/5] Stray p --- src/crypto/SecretStorage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/crypto/SecretStorage.js b/src/crypto/SecretStorage.js index 7fc978d4d..36aa59cf3 100644 --- a/src/crypto/SecretStorage.js +++ b/src/crypto/SecretStorage.js @@ -585,6 +585,5 @@ export default class SecretStorage extends EventEmitter { default: throw new Error("Unknown key type: " + keys[keyId].algorithm); } -p } }