import 'dart:async'; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; /// Sets up encryption for the account in one step, like the web client: /// a new secret storage key, new cross-signing keys, and a new key backup, /// all behind one recovery key, which is returned to be shown once. /// /// It always starts over: whatever secret storage, cross-signing keys or /// backup the account had are replaced. That is what "set up" means for an /// account without cross-signing, and what "reset" means for one that lost /// its recovery key. /// /// Uploading cross-signing keys needs the account password; the SDK asks /// for it through [Client.onUiaRequest], which the app answers. Future setUpEncryption(Encryption encryption) { final done = Completer(); BootstrapState? handled; Future step(Bootstrap b) async { // onUpdate fires once per state, but guard anyway: answering a state // twice throws. if (b.state == handled) return; handled = b.state; switch (b.state) { case BootstrapState.loading: break; case BootstrapState.askWipeSsss: b.wipeSsss(true); case BootstrapState.askUseExistingSsss: b.useExistingSsss(false); case BootstrapState.askBadSsss: b.ignoreBadSecrets(true); case BootstrapState.askUnlockSsss: b.unlockedSsss(); case BootstrapState.askNewSsss: await b.newSsss(); case BootstrapState.openExistingSsss: await b.openExistingSsss(); case BootstrapState.askWipeCrossSigning: await b.wipeCrossSigning(true); case BootstrapState.askSetupCrossSigning: await b.askSetupCrossSigning( setupMasterKey: true, setupSelfSigningKey: true, setupUserSigningKey: true, ); case BootstrapState.askWipeOnlineKeyBackup: b.wipeOnlineKeyBackup(true); case BootstrapState.askSetupOnlineKeyBackup: await b.askSetupOnlineKeyBackup(true); case BootstrapState.done: final key = b.newSsssKey?.recoveryKey; if (key == null) { done.completeError(Exception('No recovery key was made.')); } else { done.complete(key); } case BootstrapState.error: done.completeError( b.errorResult?.error ?? Exception('Setting up encryption failed.'), ); } } encryption.bootstrap( onUpdate: (b) => step(b).catchError((Object e, StackTrace s) { Logs().e('Encryption setup failed', e, s); if (!done.isCompleted) done.completeError(e, s); }), ); return done.future; } /// Verifies this session with the recovery key (or passphrase): unlocks /// secret storage, which caches the cross-signing and backup keys and signs /// this session, then restores the room keys from the backup so older /// messages can be read. Future verifyWithRecoveryKey(Encryption encryption, String input) async { final key = encryption.ssss.open(); await key.unlock(keyOrPassphrase: input.trim()); await key.maybeCacheAll(); if (!encryption.crossSigning.isSelfSigned) { await encryption.crossSigning.selfSign(openSsss: key); } if (encryption.keyManager.enabled) { // Best effort: the session is verified whether or not this finishes. unawaited( encryption.keyManager.loadAllKeys().catchError( (Object e) => Logs().w('Restoring keys from the backup failed', e), ), ); } }