LCOV - code coverage report
Current view: top level - lib/encryption/utils - bootstrap.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 81.7 % 273 223
Test Date: 2025-10-13 02:23:18 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:vodozemac/vodozemac.dart' as vod;
      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/matrix.dart';
      29              : 
      30              : enum BootstrapState {
      31              :   /// Is loading.
      32              :   loading,
      33              : 
      34              :   /// Existing SSSS found, should we wipe it?
      35              :   askWipeSsss,
      36              : 
      37              :   /// Ask if an existing SSSS should be userDeviceKeys
      38              :   askUseExistingSsss,
      39              : 
      40              :   /// Ask to unlock all the SSSS keys
      41              :   askUnlockSsss,
      42              : 
      43              :   /// SSSS is in a bad state, continue with potential dataloss?
      44              :   askBadSsss,
      45              : 
      46              :   /// Ask for new SSSS key / passphrase
      47              :   askNewSsss,
      48              : 
      49              :   /// Open an existing SSSS key
      50              :   openExistingSsss,
      51              : 
      52              :   /// Ask if cross signing should be wiped
      53              :   askWipeCrossSigning,
      54              : 
      55              :   /// Ask if cross signing should be set up
      56              :   askSetupCrossSigning,
      57              : 
      58              :   /// Ask if online key backup should be wiped
      59              :   askWipeOnlineKeyBackup,
      60              : 
      61              :   /// Ask if the online key backup should be set up
      62              :   askSetupOnlineKeyBackup,
      63              : 
      64              :   /// An error has been occured.
      65              :   error,
      66              : 
      67              :   /// done
      68              :   done,
      69              : }
      70              : 
      71              : /// Bootstrapping SSSS and cross-signing
      72              : class Bootstrap {
      73              :   final Encryption encryption;
      74            3 :   Client get client => encryption.client;
      75              :   void Function(Bootstrap)? onUpdate;
      76            2 :   BootstrapState get state => _state;
      77              :   BootstrapState _state = BootstrapState.loading;
      78              :   Map<String, OpenSSSS>? oldSsssKeys;
      79              :   OpenSSSS? newSsssKey;
      80              :   Map<String, String>? secretMap;
      81              : 
      82            1 :   Bootstrap({required this.encryption, this.onUpdate}) {
      83            2 :     if (analyzeSecrets().isNotEmpty) {
      84            1 :       state = BootstrapState.askWipeSsss;
      85              :     } else {
      86            1 :       state = BootstrapState.askNewSsss;
      87              :     }
      88              :   }
      89              : 
      90              :   // cache the secret analyzing so that we don't drop stuff a different client sets during bootstrapping
      91              :   Map<String, Set<String>>? _secretsCache;
      92              : 
      93              :   /// returns ssss from accountdata, eg: m.megolm_backup.v1, or your m.cross_signing stuff
      94            1 :   Map<String, Set<String>> analyzeSecrets() {
      95            1 :     final secretsCache = _secretsCache;
      96              :     if (secretsCache != null) {
      97              :       // deep-copy so that we can do modifications
      98            1 :       final newSecrets = <String, Set<String>>{};
      99            2 :       for (final s in secretsCache.entries) {
     100            4 :         newSecrets[s.key] = Set<String>.from(s.value);
     101              :       }
     102              :       return newSecrets;
     103              :     }
     104            1 :     final secrets = <String, Set<String>>{};
     105            4 :     for (final entry in client.accountData.entries) {
     106            1 :       final type = entry.key;
     107            1 :       final event = entry.value;
     108              :       final encryptedContent =
     109            2 :           event.content.tryGetMap<String, Object?>('encrypted');
     110              :       if (encryptedContent == null) {
     111              :         continue;
     112              :       }
     113              :       final validKeys = <String>{};
     114              :       final invalidKeys = <String>{};
     115            2 :       for (final keyEntry in encryptedContent.entries) {
     116            1 :         final key = keyEntry.key;
     117            1 :         final value = keyEntry.value;
     118            1 :         if (value is! Map) {
     119              :           // we don't add the key to invalidKeys as this was not a proper secret anyways!
     120              :           continue;
     121              :         }
     122            2 :         if (value['iv'] is! String ||
     123            2 :             value['ciphertext'] is! String ||
     124            2 :             value['mac'] is! String) {
     125            0 :           invalidKeys.add(key);
     126              :           continue;
     127              :         }
     128            3 :         if (!encryption.ssss.isKeyValid(key)) {
     129            1 :           invalidKeys.add(key);
     130              :           continue;
     131              :         }
     132            1 :         validKeys.add(key);
     133              :       }
     134            2 :       if (validKeys.isEmpty && invalidKeys.isEmpty) {
     135              :         continue; // this didn't contain any keys anyways!
     136              :       }
     137              :       // if there are no valid keys and only invalid keys then the validKeys set will be empty
     138              :       // from that we know that there were errors with this secret and that we won't be able to migrate it
     139            1 :       secrets[type] = validKeys;
     140              :     }
     141            1 :     _secretsCache = secrets;
     142            1 :     return analyzeSecrets();
     143              :   }
     144              : 
     145            1 :   Set<String> badSecrets() {
     146            1 :     final secrets = analyzeSecrets();
     147            3 :     secrets.removeWhere((k, v) => v.isNotEmpty);
     148            2 :     return Set<String>.from(secrets.keys);
     149              :   }
     150              : 
     151            1 :   String mostUsedKey(Map<String, Set<String>> secrets) {
     152            1 :     final usage = <String, int>{};
     153            2 :     for (final keys in secrets.values) {
     154            2 :       for (final key in keys) {
     155            2 :         usage.update(key, (i) => i + 1, ifAbsent: () => 1);
     156              :       }
     157              :     }
     158            2 :     final entriesList = usage.entries.toList();
     159            1 :     entriesList.sort((a, b) => a.value.compareTo(b.value));
     160            2 :     return entriesList.first.key;
     161              :   }
     162              : 
     163            1 :   Set<String> allNeededKeys() {
     164            1 :     final secrets = analyzeSecrets();
     165            1 :     secrets.removeWhere(
     166            2 :       (k, v) => v.isEmpty,
     167              :     ); // 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              :         );
     302            0 :         await client.oneShotSync();
     303              :       }
     304            1 :       if (oldSsssKeys != null) {
     305            3 :         for (final entry in secretMap!.entries) {
     306            4 :           Logs().v('Validate and stripe other keys ${entry.key}...');
     307            4 :           await newSsssKey!.validateAndStripOtherKeys(entry.key, entry.value);
     308              :         }
     309            2 :         Logs().v('And make super sure we have everything cached...');
     310            2 :         await newSsssKey!.maybeCacheAll();
     311              :       }
     312              :     } catch (e, s) {
     313            0 :       Logs().e('[Bootstrapping] Error trying to migrate old secrets', e, s);
     314            0 :       state = BootstrapState.error;
     315              :       return;
     316              :     }
     317              :     // alright, we successfully migrated all secrets, if needed
     318              : 
     319            1 :     checkCrossSigning();
     320              :   }
     321              : 
     322            0 :   Future<void> openExistingSsss() async {
     323            0 :     final newSsssKey = this.newSsssKey;
     324            0 :     if (state != BootstrapState.openExistingSsss || newSsssKey == null) {
     325            0 :       throw BootstrapBadStateException();
     326              :     }
     327            0 :     if (!newSsssKey.isUnlocked) {
     328            0 :       throw BootstrapBadStateException('Key not unlocked');
     329              :     }
     330            0 :     Logs().v('Maybe cache all...');
     331            0 :     await newSsssKey.maybeCacheAll();
     332            0 :     checkCrossSigning();
     333              :   }
     334              : 
     335            1 :   void checkCrossSigning() {
     336              :     // so, let's see if we have cross signing set up
     337            3 :     if (encryption.crossSigning.enabled) {
     338              :       // cross signing present, ask for wipe
     339            1 :       state = BootstrapState.askWipeCrossSigning;
     340              :       return;
     341              :     }
     342              :     // no cross signing present
     343            1 :     state = BootstrapState.askSetupCrossSigning;
     344              :   }
     345              : 
     346            1 :   Future<void> wipeCrossSigning(bool wipe) async {
     347            2 :     if (state != BootstrapState.askWipeCrossSigning) {
     348            0 :       throw BootstrapBadStateException();
     349              :     }
     350              :     if (wipe) {
     351            1 :       state = BootstrapState.askSetupCrossSigning;
     352              :     } else {
     353            3 :       await client.dehydratedDeviceSetup(newSsssKey!);
     354            1 :       checkOnlineKeyBackup();
     355              :     }
     356              :   }
     357              : 
     358            1 :   Future<void> askSetupCrossSigning({
     359              :     bool setupMasterKey = false,
     360              :     bool setupSelfSigningKey = false,
     361              :     bool setupUserSigningKey = false,
     362              :   }) async {
     363            2 :     if (state != BootstrapState.askSetupCrossSigning) {
     364            0 :       throw BootstrapBadStateException();
     365              :     }
     366              :     if (!setupMasterKey && !setupSelfSigningKey && !setupUserSigningKey) {
     367            3 :       await client.dehydratedDeviceSetup(newSsssKey!);
     368            1 :       checkOnlineKeyBackup();
     369              :       return;
     370              :     }
     371            2 :     final userID = client.userID!;
     372              :     try {
     373              :       String masterSigningKey;
     374            1 :       final secretsToStore = <String, String>{};
     375              :       MatrixCrossSigningKey? masterKey;
     376              :       MatrixCrossSigningKey? selfSigningKey;
     377              :       MatrixCrossSigningKey? userSigningKey;
     378              :       String? masterPub;
     379              :       if (setupMasterKey) {
     380            1 :         final master = vod.PkSigning();
     381            1 :         masterSigningKey = master.secretKey;
     382            2 :         masterPub = master.publicKey.toBase64();
     383            1 :         final json = <String, dynamic>{
     384              :           'user_id': userID,
     385            1 :           'usage': ['master'],
     386            1 :           'keys': <String, dynamic>{
     387            1 :             'ed25519:$masterPub': masterPub,
     388              :           },
     389              :         };
     390            1 :         masterKey = MatrixCrossSigningKey.fromJson(json);
     391            1 :         secretsToStore[EventTypes.CrossSigningMasterKey] = masterSigningKey;
     392              :       } else {
     393            0 :         Logs().v('Get stored key...');
     394              :         masterSigningKey =
     395            0 :             await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ?? '';
     396            0 :         if (masterSigningKey.isEmpty) {
     397              :           // no master signing key :(
     398            0 :           throw BootstrapBadStateException('No master key');
     399              :         }
     400            0 :         final master = vod.PkSigning.fromSecretKey(masterSigningKey);
     401            0 :         masterPub = master.publicKey.toBase64();
     402              :       }
     403            1 :       String? sign(Map<String, dynamic> object) {
     404            1 :         final keyObj = vod.PkSigning.fromSecretKey(masterSigningKey);
     405              :         return keyObj
     406            3 :             .sign(String.fromCharCodes(canonicalJson.encode(object)))
     407            1 :             .toBase64();
     408              :       }
     409              : 
     410              :       if (setupSelfSigningKey) {
     411            1 :         final selfSigning = vod.PkSigning();
     412            1 :         final selfSigningPriv = selfSigning.secretKey;
     413            2 :         final selfSigningPub = selfSigning.publicKey.toBase64();
     414            1 :         final json = <String, dynamic>{
     415              :           'user_id': userID,
     416            1 :           'usage': ['self_signing'],
     417            1 :           'keys': <String, dynamic>{
     418            1 :             'ed25519:$selfSigningPub': selfSigningPub,
     419              :           },
     420              :         };
     421            1 :         final signature = sign(json);
     422            2 :         json['signatures'] = <String, dynamic>{
     423            1 :           userID: <String, dynamic>{
     424            1 :             'ed25519:$masterPub': signature,
     425              :           },
     426              :         };
     427            1 :         selfSigningKey = MatrixCrossSigningKey.fromJson(json);
     428            1 :         secretsToStore[EventTypes.CrossSigningSelfSigning] = selfSigningPriv;
     429              :       }
     430              :       if (setupUserSigningKey) {
     431            1 :         final userSigning = vod.PkSigning();
     432            1 :         final userSigningPriv = userSigning.secretKey;
     433            2 :         final userSigningPub = userSigning.publicKey.toBase64();
     434            1 :         final json = <String, dynamic>{
     435              :           'user_id': userID,
     436            1 :           'usage': ['user_signing'],
     437            1 :           'keys': <String, dynamic>{
     438            1 :             'ed25519:$userSigningPub': userSigningPub,
     439              :           },
     440              :         };
     441            1 :         final signature = sign(json);
     442            2 :         json['signatures'] = <String, dynamic>{
     443            1 :           userID: <String, dynamic>{
     444            1 :             'ed25519:$masterPub': signature,
     445              :           },
     446              :         };
     447            1 :         userSigningKey = MatrixCrossSigningKey.fromJson(json);
     448            1 :         secretsToStore[EventTypes.CrossSigningUserSigning] = userSigningPriv;
     449              :       }
     450              :       // upload the keys!
     451            1 :       state = BootstrapState.loading;
     452            2 :       Logs().v('Upload device signing keys.');
     453            2 :       await client.uiaRequestBackground(
     454            3 :         (AuthenticationData? auth) => client.uploadCrossSigningKeys(
     455              :           masterKey: masterKey,
     456              :           selfSigningKey: selfSigningKey,
     457              :           userSigningKey: userSigningKey,
     458              :           auth: auth,
     459              :         ),
     460              :       );
     461            2 :       Logs().v('Device signing keys have been uploaded.');
     462              :       // aaaand set the SSSS secrets
     463              :       if (masterKey != null) {
     464            1 :         while (!(masterKey.publicKey != null &&
     465            8 :             client.userDeviceKeys[client.userID]?.masterKey?.ed25519Key ==
     466            1 :                 masterKey.publicKey)) {
     467            0 :           Logs().v('Waiting for master to be created');
     468            0 :           await client.oneShotSync();
     469              :         }
     470              :       }
     471            1 :       if (newSsssKey != null) {
     472            1 :         final storeFutures = <Future<void>>[];
     473            2 :         for (final entry in secretsToStore.entries) {
     474            5 :           storeFutures.add(newSsssKey!.store(entry.key, entry.value));
     475              :         }
     476            2 :         Logs().v('Store new SSSS key entries...');
     477            1 :         await Future.wait(storeFutures);
     478              :       }
     479              : 
     480            1 :       final keysToSign = <SignableKey>[];
     481              :       if (masterKey != null) {
     482            8 :         if (client.userDeviceKeys[client.userID]?.masterKey?.ed25519Key !=
     483            1 :             masterKey.publicKey) {
     484            0 :           throw BootstrapBadStateException(
     485              :             'ERROR: New master key does not match up!',
     486              :           );
     487              :         }
     488            2 :         Logs().v('Set own master key to verified...');
     489            6 :         await client.userDeviceKeys[client.userID]!.masterKey!
     490            1 :             .setVerified(true, false);
     491            7 :         keysToSign.add(client.userDeviceKeys[client.userID]!.masterKey!);
     492              :       }
     493              :       if (selfSigningKey != null) {
     494            1 :         keysToSign.add(
     495            9 :           client.userDeviceKeys[client.userID]!.deviceKeys[client.deviceID]!,
     496              :         );
     497              :       }
     498            2 :       Logs().v('Sign ourself...');
     499            3 :       await encryption.crossSigning.sign(keysToSign);
     500              :     } catch (e, s) {
     501            0 :       Logs().e('[Bootstrapping] Error setting up cross signing', e, s);
     502            0 :       state = BootstrapState.error;
     503              :       return;
     504              :     }
     505              : 
     506            3 :     await client.dehydratedDeviceSetup(newSsssKey!);
     507            1 :     checkOnlineKeyBackup();
     508              :   }
     509              : 
     510            1 :   void checkOnlineKeyBackup() {
     511              :     // check if we have online key backup set up
     512            3 :     if (encryption.keyManager.enabled) {
     513            1 :       state = BootstrapState.askWipeOnlineKeyBackup;
     514              :       return;
     515              :     }
     516            1 :     state = BootstrapState.askSetupOnlineKeyBackup;
     517              :   }
     518              : 
     519            1 :   void wipeOnlineKeyBackup(bool wipe) {
     520            2 :     if (state != BootstrapState.askWipeOnlineKeyBackup) {
     521            0 :       throw BootstrapBadStateException();
     522              :     }
     523              :     if (wipe) {
     524            1 :       state = BootstrapState.askSetupOnlineKeyBackup;
     525              :     } else {
     526            1 :       state = BootstrapState.done;
     527              :     }
     528              :   }
     529              : 
     530            1 :   Future<void> askSetupOnlineKeyBackup(bool setup) async {
     531            2 :     if (state != BootstrapState.askSetupOnlineKeyBackup) {
     532            0 :       throw BootstrapBadStateException();
     533              :     }
     534              :     if (!setup) {
     535            1 :       state = BootstrapState.done;
     536              :       return;
     537              :     }
     538              :     try {
     539            1 :       final keyObj = vod.PkDecryption();
     540              :       String pubKey;
     541              :       Uint8List privKey;
     542              : 
     543            1 :       pubKey = keyObj.publicKey;
     544            1 :       privKey = keyObj.privateKey;
     545              : 
     546            2 :       Logs().v('Create the new backup version...');
     547            2 :       await client.postRoomKeysVersion(
     548              :         BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2,
     549            1 :         <String, dynamic>{
     550              :           'public_key': pubKey,
     551              :         },
     552              :       );
     553            2 :       Logs().v('Store the secret...');
     554            3 :       await newSsssKey?.store(megolmKey, base64.encode(privKey));
     555              : 
     556            2 :       Logs().v(
     557              :         'And finally set all megolm keys as needing to be uploaded again...',
     558              :       );
     559            3 :       await client.database.markInboundGroupSessionsAsNeedingUpload();
     560            2 :       Logs().v('And uploading keys...');
     561            4 :       await client.encryption?.keyManager.uploadInboundGroupSessions();
     562              :     } catch (e, s) {
     563            0 :       Logs().e('[Bootstrapping] Error setting up online key backup', e, s);
     564            0 :       state = BootstrapState.error;
     565            0 :       encryption.client.onEncryptionError.add(
     566            0 :         SdkError(exception: e, stackTrace: s),
     567              :       );
     568              :       return;
     569              :     }
     570            1 :     state = BootstrapState.done;
     571              :   }
     572              : 
     573            1 :   set state(BootstrapState newState) {
     574            3 :     Logs().v('BootstrapState: $newState');
     575            2 :     if (state != BootstrapState.error) {
     576            1 :       _state = newState;
     577              :     }
     578              : 
     579            2 :     onUpdate?.call(this);
     580              :   }
     581              : }
     582              : 
     583              : class BootstrapBadStateException implements Exception {
     584              :   String cause;
     585            0 :   BootstrapBadStateException([this.cause = 'Bad state']);
     586              : 
     587            0 :   @override
     588            0 :   String toString() => 'BootstrapBadStateException: $cause';
     589              : }
        

Generated by: LCOV version 2.0-1