LCOV - code coverage report
Current view: top level - lib/encryption/utils - bootstrap.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 231 283 81.6 %
Date: 2024-05-13 12:56:47 Functions: 0 0 -

          Line data    Source code
       1             : /*
       2             :  *   Famedly Matrix SDK
       3             :  *   Copyright (C) 2020, 2021 Famedly GmbH
       4             :  *
       5             :  *   This program is free software: you can redistribute it and/or modify
       6             :  *   it under the terms of the GNU Affero General Public License as
       7             :  *   published by the Free Software Foundation, either version 3 of the
       8             :  *   License, or (at your option) any later version.
       9             :  *
      10             :  *   This program is distributed in the hope that it will be useful,
      11             :  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      13             :  *   GNU Affero General Public License for more details.
      14             :  *
      15             :  *   You should have received a copy of the GNU Affero General Public License
      16             :  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
      17             :  */
      18             : 
      19             : import 'dart:convert';
      20             : import 'dart:typed_data';
      21             : 
      22             : import 'package:canonical_json/canonical_json.dart';
      23             : import 'package:olm/olm.dart' as olm;
      24             : 
      25             : import 'package:matrix/encryption/encryption.dart';
      26             : import 'package:matrix/encryption/key_manager.dart';
      27             : import 'package:matrix/encryption/ssss.dart';
      28             : import 'package:matrix/encryption/utils/base64_unpadded.dart';
      29             : import 'package:matrix/matrix.dart';
      30             : 
      31             : enum BootstrapState {
      32             :   /// Is loading.
      33             :   loading,
      34             : 
      35             :   /// Existing SSSS found, should we wipe it?
      36             :   askWipeSsss,
      37             : 
      38             :   /// Ask if an existing SSSS should be userDeviceKeys
      39             :   askUseExistingSsss,
      40             : 
      41             :   /// Ask to unlock all the SSSS keys
      42             :   askUnlockSsss,
      43             : 
      44             :   /// SSSS is in a bad state, continue with potential dataloss?
      45             :   askBadSsss,
      46             : 
      47             :   /// Ask for new SSSS key / passphrase
      48             :   askNewSsss,
      49             : 
      50             :   /// Open an existing SSSS key
      51             :   openExistingSsss,
      52             : 
      53             :   /// Ask if cross signing should be wiped
      54             :   askWipeCrossSigning,
      55             : 
      56             :   /// Ask if cross signing should be set up
      57             :   askSetupCrossSigning,
      58             : 
      59             :   /// Ask if online key backup should be wiped
      60             :   askWipeOnlineKeyBackup,
      61             : 
      62             :   /// Ask if the online key backup should be set up
      63             :   askSetupOnlineKeyBackup,
      64             : 
      65             :   /// An error has been occured.
      66             :   error,
      67             : 
      68             :   /// done
      69             :   done,
      70             : }
      71             : 
      72             : /// Bootstrapping SSSS and cross-signing
      73             : class Bootstrap {
      74             :   final Encryption encryption;
      75           3 :   Client get client => encryption.client;
      76             :   void Function(Bootstrap)? onUpdate;
      77           2 :   BootstrapState get state => _state;
      78             :   BootstrapState _state = BootstrapState.loading;
      79             :   Map<String, OpenSSSS>? oldSsssKeys;
      80             :   OpenSSSS? newSsssKey;
      81             :   Map<String, String>? secretMap;
      82             : 
      83           1 :   Bootstrap({required this.encryption, this.onUpdate}) {
      84           2 :     if (analyzeSecrets().isNotEmpty) {
      85           1 :       state = BootstrapState.askWipeSsss;
      86             :     } else {
      87           1 :       state = BootstrapState.askNewSsss;
      88             :     }
      89             :   }
      90             : 
      91             :   // cache the secret analyzing so that we don't drop stuff a different client sets during bootstrapping
      92             :   Map<String, Set<String>>? _secretsCache;
      93             : 
      94             :   /// returns ssss from accountdata, eg: m.megolm_backup.v1, or your m.cross_signing stuff
      95           1 :   Map<String, Set<String>> analyzeSecrets() {
      96           1 :     final secretsCache = _secretsCache;
      97             :     if (secretsCache != null) {
      98             :       // deep-copy so that we can do modifications
      99           1 :       final newSecrets = <String, Set<String>>{};
     100           2 :       for (final s in secretsCache.entries) {
     101           4 :         newSecrets[s.key] = Set<String>.from(s.value);
     102             :       }
     103             :       return newSecrets;
     104             :     }
     105           1 :     final secrets = <String, Set<String>>{};
     106           4 :     for (final entry in client.accountData.entries) {
     107           1 :       final type = entry.key;
     108           1 :       final event = entry.value;
     109             :       final encryptedContent =
     110           2 :           event.content.tryGetMap<String, Object?>('encrypted');
     111             :       if (encryptedContent == null) {
     112             :         continue;
     113             :       }
     114             :       final validKeys = <String>{};
     115             :       final invalidKeys = <String>{};
     116           2 :       for (final keyEntry in encryptedContent.entries) {
     117           1 :         final key = keyEntry.key;
     118           1 :         final value = keyEntry.value;
     119           1 :         if (value is! Map) {
     120             :           // we don't add the key to invalidKeys as this was not a proper secret anyways!
     121             :           continue;
     122             :         }
     123           2 :         if (value['iv'] is! String ||
     124           2 :             value['ciphertext'] is! String ||
     125           2 :             value['mac'] is! String) {
     126           0 :           invalidKeys.add(key);
     127             :           continue;
     128             :         }
     129           3 :         if (!encryption.ssss.isKeyValid(key)) {
     130           1 :           invalidKeys.add(key);
     131             :           continue;
     132             :         }
     133           1 :         validKeys.add(key);
     134             :       }
     135           2 :       if (validKeys.isEmpty && invalidKeys.isEmpty) {
     136             :         continue; // this didn't contain any keys anyways!
     137             :       }
     138             :       // if there are no valid keys and only invalid keys then the validKeys set will be empty
     139             :       // from that we know that there were errors with this secret and that we won't be able to migrate it
     140           1 :       secrets[type] = validKeys;
     141             :     }
     142           1 :     _secretsCache = secrets;
     143           1 :     return analyzeSecrets();
     144             :   }
     145             : 
     146           1 :   Set<String> badSecrets() {
     147           1 :     final secrets = analyzeSecrets();
     148           3 :     secrets.removeWhere((k, v) => v.isNotEmpty);
     149           2 :     return Set<String>.from(secrets.keys);
     150             :   }
     151             : 
     152           1 :   String mostUsedKey(Map<String, Set<String>> secrets) {
     153           1 :     final usage = <String, int>{};
     154           2 :     for (final keys in secrets.values) {
     155           2 :       for (final key in keys) {
     156           2 :         usage.update(key, (i) => i + 1, ifAbsent: () => 1);
     157             :       }
     158             :     }
     159           2 :     final entriesList = usage.entries.toList();
     160           1 :     entriesList.sort((a, b) => a.value.compareTo(b.value));
     161           2 :     return entriesList.first.key;
     162             :   }
     163             : 
     164           1 :   Set<String> allNeededKeys() {
     165           1 :     final secrets = analyzeSecrets();
     166           1 :     secrets.removeWhere(
     167           2 :         (k, v) => v.isEmpty); // we don't care about the failed secrets here
     168             :     final keys = <String>{};
     169           3 :     final defaultKeyId = encryption.ssss.defaultKeyId;
     170           1 :     int removeKey(String key) {
     171           1 :       final sizeBefore = secrets.length;
     172           3 :       secrets.removeWhere((k, v) => v.contains(key));
     173           2 :       return sizeBefore - secrets.length;
     174             :     }
     175             : 
     176             :     // first we want to try the default key id
     177             :     if (defaultKeyId != null) {
     178           2 :       if (removeKey(defaultKeyId) > 0) {
     179           1 :         keys.add(defaultKeyId);
     180             :       }
     181             :     }
     182             :     // now we re-try as long as we have keys for all secrets
     183           1 :     while (secrets.isNotEmpty) {
     184           1 :       final key = mostUsedKey(secrets);
     185           1 :       removeKey(key);
     186           1 :       keys.add(key);
     187             :     }
     188             :     return keys;
     189             :   }
     190             : 
     191           1 :   void wipeSsss(bool wipe) {
     192           2 :     if (state != BootstrapState.askWipeSsss) {
     193           0 :       throw BootstrapBadStateException('Wrong State');
     194             :     }
     195             :     if (wipe) {
     196           1 :       state = BootstrapState.askNewSsss;
     197           3 :     } else if (encryption.ssss.defaultKeyId != null &&
     198           6 :         encryption.ssss.isKeyValid(encryption.ssss.defaultKeyId!)) {
     199           1 :       state = BootstrapState.askUseExistingSsss;
     200           2 :     } else if (badSecrets().isNotEmpty) {
     201           1 :       state = BootstrapState.askBadSsss;
     202             :     } else {
     203           0 :       migrateOldSsss();
     204             :     }
     205             :   }
     206             : 
     207           1 :   void useExistingSsss(bool use) {
     208           2 :     if (state != BootstrapState.askUseExistingSsss) {
     209           0 :       throw BootstrapBadStateException('Wrong State');
     210             :     }
     211             :     if (use) {
     212             :       try {
     213           0 :         newSsssKey = encryption.ssss.open(encryption.ssss.defaultKeyId);
     214           0 :         state = BootstrapState.openExistingSsss;
     215             :       } catch (e, s) {
     216           0 :         Logs().e('[Bootstrapping] Error open SSSS', e, s);
     217           0 :         state = BootstrapState.error;
     218             :         return;
     219             :       }
     220           2 :     } else if (badSecrets().isNotEmpty) {
     221           0 :       state = BootstrapState.askBadSsss;
     222             :     } else {
     223           1 :       migrateOldSsss();
     224             :     }
     225             :   }
     226             : 
     227           1 :   void ignoreBadSecrets(bool ignore) {
     228           2 :     if (state != BootstrapState.askBadSsss) {
     229           0 :       throw BootstrapBadStateException('Wrong State');
     230             :     }
     231             :     if (ignore) {
     232           0 :       migrateOldSsss();
     233             :     } else {
     234             :       // that's it, folks. We can't do anything here
     235           1 :       state = BootstrapState.error;
     236             :     }
     237             :   }
     238             : 
     239           1 :   void migrateOldSsss() {
     240           1 :     final keys = allNeededKeys();
     241           2 :     final oldSsssKeys = this.oldSsssKeys = {};
     242             :     try {
     243           2 :       for (final key in keys) {
     244           4 :         oldSsssKeys[key] = encryption.ssss.open(key);
     245             :       }
     246             :     } catch (e, s) {
     247           0 :       Logs().e('[Bootstrapping] Error construction ssss key', e, s);
     248           0 :       state = BootstrapState.error;
     249             :       return;
     250             :     }
     251           1 :     state = BootstrapState.askUnlockSsss;
     252             :   }
     253             : 
     254           1 :   void unlockedSsss() {
     255           2 :     if (state != BootstrapState.askUnlockSsss) {
     256           0 :       throw BootstrapBadStateException('Wrong State');
     257             :     }
     258           1 :     state = BootstrapState.askNewSsss;
     259             :   }
     260             : 
     261           1 :   Future<void> newSsss([String? passphrase]) async {
     262           2 :     if (state != BootstrapState.askNewSsss) {
     263           0 :       throw BootstrapBadStateException('Wrong State');
     264             :     }
     265           1 :     state = BootstrapState.loading;
     266             :     try {
     267           2 :       Logs().v('Create key...');
     268           4 :       newSsssKey = await encryption.ssss.createKey(passphrase);
     269           1 :       if (oldSsssKeys != null) {
     270             :         // alright, we have to re-encrypt old secrets with the new key
     271           1 :         final secrets = analyzeSecrets();
     272           1 :         Set<String> removeKey(String key) {
     273           1 :           final s = secrets.entries
     274           4 :               .where((e) => e.value.contains(key))
     275           3 :               .map((e) => e.key)
     276           1 :               .toSet();
     277           3 :           secrets.removeWhere((k, v) => v.contains(key));
     278             :           return s;
     279             :         }
     280             : 
     281           2 :         secretMap = <String, String>{};
     282           3 :         for (final entry in oldSsssKeys!.entries) {
     283           1 :           final key = entry.value;
     284           1 :           final keyId = entry.key;
     285           1 :           if (!key.isUnlocked) {
     286             :             continue;
     287             :           }
     288           2 :           for (final s in removeKey(keyId)) {
     289           3 :             Logs().v('Get stored key of type $s...');
     290           3 :             secretMap![s] = await key.getStored(s);
     291           2 :             Logs().v('Store new secret with this key...');
     292           4 :             await newSsssKey!.store(s, secretMap![s]!, add: true);
     293             :           }
     294             :         }
     295             :         // alright, we re-encrypted all the secrets. We delete the dead weight only *after* we set our key to the default key
     296             :       }
     297           5 :       await encryption.ssss.setDefaultKeyId(newSsssKey!.keyId);
     298           6 :       while (encryption.ssss.defaultKeyId != newSsssKey!.keyId) {
     299           0 :         Logs().v(
     300             :             'Waiting accountData to have the correct m.secret_storage.default_key');
     301           0 :         await client.oneShotSync();
     302             :       }
     303           1 :       if (oldSsssKeys != null) {
     304           3 :         for (final entry in secretMap!.entries) {
     305           4 :           Logs().v('Validate and stripe other keys ${entry.key}...');
     306           4 :           await newSsssKey!.validateAndStripOtherKeys(entry.key, entry.value);
     307             :         }
     308           2 :         Logs().v('And make super sure we have everything cached...');
     309           2 :         await newSsssKey!.maybeCacheAll();
     310             :       }
     311             :     } catch (e, s) {
     312           0 :       Logs().e('[Bootstrapping] Error trying to migrate old secrets', e, s);
     313           0 :       state = BootstrapState.error;
     314             :       return;
     315             :     }
     316             :     // alright, we successfully migrated all secrets, if needed
     317             : 
     318           1 :     checkCrossSigning();
     319             :   }
     320             : 
     321           0 :   Future<void> openExistingSsss() async {
     322           0 :     final newSsssKey = this.newSsssKey;
     323           0 :     if (state != BootstrapState.openExistingSsss || newSsssKey == null) {
     324           0 :       throw BootstrapBadStateException();
     325             :     }
     326           0 :     if (!newSsssKey.isUnlocked) {
     327           0 :       throw BootstrapBadStateException('Key not unlocked');
     328             :     }
     329           0 :     Logs().v('Maybe cache all...');
     330           0 :     await newSsssKey.maybeCacheAll();
     331           0 :     checkCrossSigning();
     332             :   }
     333             : 
     334           1 :   void checkCrossSigning() {
     335             :     // so, let's see if we have cross signing set up
     336           3 :     if (encryption.crossSigning.enabled) {
     337             :       // cross signing present, ask for wipe
     338           1 :       state = BootstrapState.askWipeCrossSigning;
     339             :       return;
     340             :     }
     341             :     // no cross signing present
     342           1 :     state = BootstrapState.askSetupCrossSigning;
     343             :   }
     344             : 
     345           1 :   Future<void> wipeCrossSigning(bool wipe) async {
     346           2 :     if (state != BootstrapState.askWipeCrossSigning) {
     347           0 :       throw BootstrapBadStateException();
     348             :     }
     349             :     if (wipe) {
     350           1 :       state = BootstrapState.askSetupCrossSigning;
     351             :     } else {
     352           3 :       await client.dehydratedDeviceSetup(newSsssKey!);
     353           1 :       checkOnlineKeyBackup();
     354             :     }
     355             :   }
     356             : 
     357           1 :   Future<void> askSetupCrossSigning(
     358             :       {bool setupMasterKey = false,
     359             :       bool setupSelfSigningKey = false,
     360             :       bool setupUserSigningKey = false}) async {
     361           2 :     if (state != BootstrapState.askSetupCrossSigning) {
     362           0 :       throw BootstrapBadStateException();
     363             :     }
     364             :     if (!setupMasterKey && !setupSelfSigningKey && !setupUserSigningKey) {
     365           3 :       await client.dehydratedDeviceSetup(newSsssKey!);
     366           1 :       checkOnlineKeyBackup();
     367             :       return;
     368             :     }
     369           2 :     final userID = client.userID!;
     370             :     try {
     371             :       Uint8List masterSigningKey;
     372           1 :       final secretsToStore = <String, String>{};
     373             :       MatrixCrossSigningKey? masterKey;
     374             :       MatrixCrossSigningKey? selfSigningKey;
     375             :       MatrixCrossSigningKey? userSigningKey;
     376             :       String? masterPub;
     377             :       if (setupMasterKey) {
     378           1 :         final master = olm.PkSigning();
     379             :         try {
     380           1 :           masterSigningKey = master.generate_seed();
     381           1 :           masterPub = master.init_with_seed(masterSigningKey);
     382           1 :           final json = <String, dynamic>{
     383             :             'user_id': userID,
     384           1 :             'usage': ['master'],
     385           1 :             'keys': <String, dynamic>{
     386           1 :               'ed25519:$masterPub': masterPub,
     387             :             },
     388             :           };
     389           1 :           masterKey = MatrixCrossSigningKey.fromJson(json);
     390           1 :           secretsToStore[EventTypes.CrossSigningMasterKey] =
     391           1 :               base64.encode(masterSigningKey);
     392             :         } finally {
     393           1 :           master.free();
     394             :         }
     395             :       } else {
     396           0 :         Logs().v('Get stored key...');
     397           0 :         masterSigningKey = base64decodeUnpadded(
     398           0 :             await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ??
     399             :                 '');
     400           0 :         if (masterSigningKey.isEmpty) {
     401             :           // no master signing key :(
     402           0 :           throw BootstrapBadStateException('No master key');
     403             :         }
     404           0 :         final master = olm.PkSigning();
     405             :         try {
     406           0 :           masterPub = master.init_with_seed(masterSigningKey);
     407             :         } finally {
     408           0 :           master.free();
     409             :         }
     410             :       }
     411           1 :       String? sign(Map<String, dynamic> object) {
     412           1 :         final keyObj = olm.PkSigning();
     413             :         try {
     414           1 :           keyObj.init_with_seed(masterSigningKey);
     415             :           return keyObj
     416           3 :               .sign(String.fromCharCodes(canonicalJson.encode(object)));
     417             :         } finally {
     418           1 :           keyObj.free();
     419             :         }
     420             :       }
     421             : 
     422             :       if (setupSelfSigningKey) {
     423           1 :         final selfSigning = olm.PkSigning();
     424             :         try {
     425           1 :           final selfSigningPriv = selfSigning.generate_seed();
     426           1 :           final selfSigningPub = selfSigning.init_with_seed(selfSigningPriv);
     427           1 :           final json = <String, dynamic>{
     428             :             'user_id': userID,
     429           1 :             'usage': ['self_signing'],
     430           1 :             'keys': <String, dynamic>{
     431           1 :               'ed25519:$selfSigningPub': selfSigningPub,
     432             :             },
     433             :           };
     434           1 :           final signature = sign(json);
     435           2 :           json['signatures'] = <String, dynamic>{
     436           1 :             userID: <String, dynamic>{
     437           1 :               'ed25519:$masterPub': signature,
     438             :             },
     439             :           };
     440           1 :           selfSigningKey = MatrixCrossSigningKey.fromJson(json);
     441           1 :           secretsToStore[EventTypes.CrossSigningSelfSigning] =
     442           1 :               base64.encode(selfSigningPriv);
     443             :         } finally {
     444           1 :           selfSigning.free();
     445             :         }
     446             :       }
     447             :       if (setupUserSigningKey) {
     448           1 :         final userSigning = olm.PkSigning();
     449             :         try {
     450           1 :           final userSigningPriv = userSigning.generate_seed();
     451           1 :           final userSigningPub = userSigning.init_with_seed(userSigningPriv);
     452           1 :           final json = <String, dynamic>{
     453             :             'user_id': userID,
     454           1 :             'usage': ['user_signing'],
     455           1 :             'keys': <String, dynamic>{
     456           1 :               'ed25519:$userSigningPub': userSigningPub,
     457             :             },
     458             :           };
     459           1 :           final signature = sign(json);
     460           2 :           json['signatures'] = <String, dynamic>{
     461           1 :             userID: <String, dynamic>{
     462           1 :               'ed25519:$masterPub': signature,
     463             :             },
     464             :           };
     465           1 :           userSigningKey = MatrixCrossSigningKey.fromJson(json);
     466           1 :           secretsToStore[EventTypes.CrossSigningUserSigning] =
     467           1 :               base64.encode(userSigningPriv);
     468             :         } finally {
     469           1 :           userSigning.free();
     470             :         }
     471             :       }
     472             :       // upload the keys!
     473           1 :       state = BootstrapState.loading;
     474           2 :       Logs().v('Upload device signing keys.');
     475           2 :       await client.uiaRequestBackground(
     476           3 :           (AuthenticationData? auth) => client.uploadCrossSigningKeys(
     477             :                 masterKey: masterKey,
     478             :                 selfSigningKey: selfSigningKey,
     479             :                 userSigningKey: userSigningKey,
     480             :                 auth: auth,
     481             :               ));
     482           2 :       Logs().v('Device signing keys have been uploaded.');
     483             :       // aaaand set the SSSS secrets
     484             :       if (masterKey != null) {
     485           1 :         while (!(masterKey.publicKey != null &&
     486           8 :             client.userDeviceKeys[client.userID]?.masterKey?.ed25519Key ==
     487           1 :                 masterKey.publicKey)) {
     488           0 :           Logs().v('Waiting for master to be created');
     489           0 :           await client.oneShotSync();
     490             :         }
     491             :       }
     492           1 :       if (newSsssKey != null) {
     493           1 :         final storeFutures = <Future<void>>[];
     494           2 :         for (final entry in secretsToStore.entries) {
     495           5 :           storeFutures.add(newSsssKey!.store(entry.key, entry.value));
     496             :         }
     497           2 :         Logs().v('Store new SSSS key entries...');
     498           1 :         await Future.wait(storeFutures);
     499             :       }
     500             : 
     501           1 :       final keysToSign = <SignableKey>[];
     502             :       if (masterKey != null) {
     503           8 :         if (client.userDeviceKeys[client.userID]?.masterKey?.ed25519Key !=
     504           1 :             masterKey.publicKey) {
     505           0 :           throw BootstrapBadStateException(
     506             :               'ERROR: New master key does not match up!');
     507             :         }
     508           2 :         Logs().v('Set own master key to verified...');
     509           6 :         await client.userDeviceKeys[client.userID]!.masterKey!
     510           1 :             .setVerified(true, false);
     511           7 :         keysToSign.add(client.userDeviceKeys[client.userID]!.masterKey!);
     512             :       }
     513             :       if (selfSigningKey != null) {
     514           1 :         keysToSign.add(
     515           9 :             client.userDeviceKeys[client.userID]!.deviceKeys[client.deviceID]!);
     516             :       }
     517           2 :       Logs().v('Sign ourself...');
     518           3 :       await encryption.crossSigning.sign(keysToSign);
     519             :     } catch (e, s) {
     520           0 :       Logs().e('[Bootstrapping] Error setting up cross signing', e, s);
     521           0 :       state = BootstrapState.error;
     522             :       return;
     523             :     }
     524             : 
     525           3 :     await client.dehydratedDeviceSetup(newSsssKey!);
     526           1 :     checkOnlineKeyBackup();
     527             :   }
     528             : 
     529           1 :   void checkOnlineKeyBackup() {
     530             :     // check if we have online key backup set up
     531           3 :     if (encryption.keyManager.enabled) {
     532           1 :       state = BootstrapState.askWipeOnlineKeyBackup;
     533             :       return;
     534             :     }
     535           1 :     state = BootstrapState.askSetupOnlineKeyBackup;
     536             :   }
     537             : 
     538           1 :   void wipeOnlineKeyBackup(bool wipe) {
     539           2 :     if (state != BootstrapState.askWipeOnlineKeyBackup) {
     540           0 :       throw BootstrapBadStateException();
     541             :     }
     542             :     if (wipe) {
     543           1 :       state = BootstrapState.askSetupOnlineKeyBackup;
     544             :     } else {
     545           1 :       state = BootstrapState.done;
     546             :     }
     547             :   }
     548             : 
     549           1 :   Future<void> askSetupOnlineKeyBackup(bool setup) async {
     550           2 :     if (state != BootstrapState.askSetupOnlineKeyBackup) {
     551           0 :       throw BootstrapBadStateException();
     552             :     }
     553             :     if (!setup) {
     554           1 :       state = BootstrapState.done;
     555             :       return;
     556             :     }
     557             :     try {
     558           1 :       final keyObj = olm.PkDecryption();
     559             :       String pubKey;
     560             :       Uint8List privKey;
     561             :       try {
     562           1 :         pubKey = keyObj.generate_key();
     563           1 :         privKey = keyObj.get_private_key();
     564             :       } finally {
     565           1 :         keyObj.free();
     566             :       }
     567           2 :       Logs().v('Create the new backup version...');
     568           2 :       await client.postRoomKeysVersion(
     569             :         BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2,
     570           1 :         <String, dynamic>{
     571             :           'public_key': pubKey,
     572             :         },
     573             :       );
     574           2 :       Logs().v('Store the secret...');
     575           3 :       await newSsssKey?.store(megolmKey, base64.encode(privKey));
     576             : 
     577           2 :       Logs().v(
     578             :           'And finally set all megolm keys as needing to be uploaded again...');
     579           3 :       await client.database?.markInboundGroupSessionsAsNeedingUpload();
     580           2 :       Logs().v('And uploading keys...');
     581           4 :       await client.encryption?.keyManager.uploadInboundGroupSessions();
     582             :     } catch (e, s) {
     583           0 :       Logs().e('[Bootstrapping] Error setting up online key backup', e, s);
     584           0 :       state = BootstrapState.error;
     585           0 :       encryption.client.onEncryptionError.add(
     586           0 :         SdkError(exception: e, stackTrace: s),
     587             :       );
     588             :       return;
     589             :     }
     590           1 :     state = BootstrapState.done;
     591             :   }
     592             : 
     593           1 :   set state(BootstrapState newState) {
     594           3 :     Logs().v('BootstrapState: $newState');
     595           2 :     if (state != BootstrapState.error) {
     596           1 :       _state = newState;
     597             :     }
     598             : 
     599           2 :     onUpdate?.call(this);
     600             :   }
     601             : }
     602             : 
     603             : class BootstrapBadStateException implements Exception {
     604             :   String cause;
     605           0 :   BootstrapBadStateException([this.cause = 'Bad state']);
     606             : 
     607           0 :   @override
     608           0 :   String toString() => 'BootstrapBadStateException: $cause';
     609             : }

Generated by: LCOV version 1.14