From 1f77cc6d1a26fd421dde19ace2e8762622873bef Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 31 Jan 2019 21:13:01 +0000 Subject: [PATCH] Cross sign the current device with the SSK whenever we get the SSK, ie. when creating or restoring a backup --- src/base-apis.js | 6 +++++ src/client.js | 18 ++++++++++----- src/crypto/index.js | 55 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/base-apis.js b/src/base-apis.js index 4926700a4..9186c3f25 100644 --- a/src/base-apis.js +++ b/src/base-apis.js @@ -1594,6 +1594,12 @@ MatrixBaseApis.prototype.uploadKeysRequest = function(content, opts, callback) { ); }; +MatrixBaseApis.prototype.uploadKeySignatures = function(content) { + return this._http.authedRequestWithPrefix( + undefined, "POST", '/keys/signatures/upload', undefined, content, httpApi.PREFIX_UNSTABLE, + ); +}; + /** * Download device keys * diff --git a/src/client.js b/src/client.js index 4ccb9325a..54dd4924f 100644 --- a/src/client.js +++ b/src/client.js @@ -1070,8 +1070,10 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, auth, replacesSsk }; return this._cryptoStore.doTxn('readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + // store the newly generated account keys this._cryptoStore.storeAccountKeys(txn, info.accountKeys); }).then(() => { + // upload the public part of the account keys return this.uploadDeviceSigningKeys(keys); }).then(() => { return this._crypto._signObject(data.auth_data); @@ -1086,6 +1088,9 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, auth, replacesSsk version: res.version, }); return res; + }).then(() => { + // upload signatures between the SSK & this device + return this._crypto.uploadDeviceKeySignatures(); }); }; @@ -1199,8 +1204,6 @@ MatrixClient.prototype._restoreKeyBackup = async function( let totalKeyCount = 0; let keys = []; - const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, backupInfo.version); - const decryption = new global.Olm.PkDecryption(); try { decryption.init_with_private_key(privKey); @@ -1237,9 +1240,14 @@ MatrixClient.prototype._restoreKeyBackup = async function( throw e; } - return this._http.authedRequest( - undefined, "GET", path.path, path.queryData, - ).then((res) => { + // start by signing this device from the SSK now we have it + return this._crypto.uploadDeviceKeySignatures().then(() => { + // Now fetch the encrypted keys + const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, backupInfo.version); + return this._http.authedRequest( + undefined, "GET", path.path, path.queryData, + ); + }).then((res) => { if (res.rooms) { for (const [roomId, roomData] of Object.entries(res.rooms)) { if (!roomData.sessions) continue; diff --git a/src/crypto/index.js b/src/crypto/index.js index 0621226d1..c4d6ba8c0 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -46,6 +46,8 @@ import { newUnknownMethodError, } from './verification/Error'; +import { pkSign } from './PkSigning'; + const defaultVerificationMethods = { [ScanQRCode.NAME]: ScanQRCode, [ShowQRCode.NAME]: ShowQRCode, @@ -457,8 +459,20 @@ Crypto.prototype.uploadDeviceKeys = function() { user_id: userId, }; + let accountKeys; return crypto._signObject(deviceKeys).then(() => { - crypto._baseApis.uploadKeysRequest({ + return this._cryptoStore.doTxn('readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this._cryptoStore.getAccountKeys(txn, keys => { + accountKeys = keys; + }); + }); + }).then(() => { + if (accountKeys && accountKeys.self_signing_key_seed) { + // if we have an SSK, sign the key with the SSK too + pkSign(deviceKeys, Buffer.from(accountKeys.self_signing_key_seed, 'base64'), userId); + } + + return crypto._baseApis.uploadKeysRequest({ device_keys: deviceKeys, }, { // for now, we set the device id explicitly, as we may not be using the @@ -468,6 +482,45 @@ Crypto.prototype.uploadDeviceKeys = function() { }); }; +/** + * If a self-signing key is available, uploads the signature of this device from + * the self-signing key + * + * @return {bool} Promise: True if signatures were uploaded or otherwise false + * (eg. if no account keys were available) + */ +Crypto.prototype.uploadDeviceKeySignatures = async function() { + const crypto = this; + const userId = crypto._userId; + const deviceId = crypto._deviceId; + + const thisDeviceKey = { + algorithms: crypto._supportedAlgorithms, + device_id: deviceId, + keys: crypto._deviceKeys, + user_id: userId, + }; + let accountKeys; + await this._cryptoStore.doTxn('readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this._cryptoStore.getAccountKeys(txn, keys => { + accountKeys = keys; + }); + }); + if (!accountKeys || !accountKeys.self_signing_key_seed) return false; + + // Sign this device with the SSK + pkSign(thisDeviceKey, Buffer.from(accountKeys.self_signing_key_seed, 'base64'), userId); + + const content = { + [userId]: { + [deviceId]: thisDeviceKey, + }, + }; + + await crypto._baseApis.uploadKeySignatures(content); + return true +}; + /** * Stores the current one_time_key count which will be handled later (in a call of * onSyncCompleted). The count is e.g. coming from a /sync response.