diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index 63af7c4766..69e586c58d 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -31,7 +31,7 @@ import {verificationMethods} from 'matrix-js-sdk/src/crypto'; import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler"; import * as StorageManager from './utils/StorageManager'; import IdentityAuthClient from './IdentityAuthClient'; -import { crossSigningCallbacks } from './SecurityManager'; +import { crossSigningCallbacks, tryToUnlockSecretStorageWithDehydrationKey } from './SecurityManager'; import {SHOW_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode"; export interface IMatrixClientCreds { @@ -193,6 +193,7 @@ class _MatrixClientPeg implements IMatrixClientPeg { this.matrixClient.setCryptoTrustCrossSignedDevices( !SettingsStore.getValue('e2ee.manuallyVerifyAllSessions'), ); + await tryToUnlockSecretStorageWithDehydrationKey(this.matrixClient); StorageManager.setCryptoInitialised(true); } } catch (e) { diff --git a/src/SecurityManager.js b/src/SecurityManager.js index 61a4c7d0a0..323c0c3a4f 100644 --- a/src/SecurityManager.js +++ b/src/SecurityManager.js @@ -34,15 +34,9 @@ let secretStorageKeys = {}; let secretStorageKeyInfo = {}; let secretStorageBeingAccessed = false; -let dehydrationInfo = {}; +let nonInteractive = false; -export function cacheDehydrationKey(key, keyInfo = {}) { - dehydrationInfo = {key, keyInfo}; -} - -export function getDehydrationKeyCache() { - return dehydrationInfo; -} +let dehydrationCache = {}; function isCachingAllowed() { return secretStorageBeingAccessed; @@ -103,18 +97,15 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { return [keyId, secretStorageKeys[keyId]]; } - // if we dehydrated a device, see if that key works for SSSS - if (dehydrationInfo.key) { - try { - const key = dehydrationInfo.key; - if (await MatrixClientPeg.get().checkSecretStorageKey(key, keyInfo)) { - // Save to cache to avoid future prompts in the current session - cacheSecretStorageKey(keyId, key, keyInfo); - dehydrationInfo = {}; - return [name, key]; - } - } catch {} - dehydrationInfo = {}; + if (dehydrationCache.key) { + if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationCache.key, keyInfo)) { + cacheSecretStorageKey(keyId, dehydrationCache.key, keyInfo); + return [keyId, dehydrationCache.key]; + } + } + + if (nonInteractive) { + throw new Error("Could not unlock non-interactively"); } const inputToKey = makeInputToKey(keyInfo); @@ -186,8 +177,10 @@ export async function getDehydrationKey(keyInfo, checkFunc) { throw new AccessCancelledError(); } const key = await inputToKey(input); + // need to copy the key because rehydration (unpickling) will clobber it - cacheDehydrationKey(key, keyInfo); + dehydrationCache = {key: new Uint8Array(key), keyInfo}; + return key; } @@ -358,3 +351,53 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f } } } + +// FIXME: this function name is a bit of a mouthful +export async function tryToUnlockSecretStorageWithDehydrationKey(client) { + const key = dehydrationCache.key; + let restoringBackup = false; + if (key && await client.isSecretStorageReady()) { + console.log("Trying to set up cross-signing using dehydration key"); + secretStorageBeingAccessed = true; + nonInteractive = true; + try { + await client.checkOwnCrossSigningTrust(); + + // we also need to set a new dehydrated device to replace the + // device we rehydrated + const dehydrationKeyInfo = + dehydrationCache.keyInfo && dehydrationCache.keyInfo.passphrase + ? {passphrase: dehydrationCache.keyInfo.passphrase} + : {}; + await client.setDehydrationKey(key, dehydrationKeyInfo); + + // and restore from backup + const backupInfo = await client.getKeyBackupVersion(); + if (backupInfo) { + restoringBackup = true; + // don't await, because this can take a long time + client.restoreKeyBackupWithSecretStorage(backupInfo) + .finally(() => { + secretStorageBeingAccessed = false; + nonInteractive = false; + if (!isCachingAllowed()) { + secretStorageKeys = {}; + secretStorageKeyInfo = {}; + } + }); + } + } finally { + dehydrationCache = {}; + // the secret storage cache is needed for restoring from backup, so + // don't clear it yet if we're restoring from backup + if (!restoringBackup) { + secretStorageBeingAccessed = false; + nonInteractive = false; + if (!isCachingAllowed()) { + secretStorageKeys = {}; + secretStorageKeyInfo = {}; + } + } + } + } +}